Embed base images into binary
Include resource directory Ran `deno fmt` with 4 space indent, that changed every file (!!!!!) various changes
2
.gitignore
vendored
@@ -132,3 +132,5 @@ dist
|
|||||||
# galvanic corrosion
|
# galvanic corrosion
|
||||||
build/
|
build/
|
||||||
config.json
|
config.json
|
||||||
|
|
||||||
|
.vscode
|
||||||
14
README.md
@@ -1,17 +1,23 @@
|
|||||||
# Galvanic Corrosion
|
# Galvanic Corrosion
|
||||||
|
|
||||||
delectable yum yum
|
delectable yum yum
|
||||||
|
|
||||||
Rec Room custom server for communities. Fast runtime and easy setup.<br>Built for Rec Room build 526 (Timestamp: 637098805133024772, Version: 20191120)
|
Rec Room custom server for communities. Fast runtime and easy setup.<br>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"/>
|
||||||
|
|
||||||
## Compiling Galvanic Corrosion
|
## Compiling Galvanic Corrosion
|
||||||
* Install [Deno 2.x](https://docs.deno.com/runtime/getting_started/installation/)
|
|
||||||
* Configure project
|
- Install
|
||||||
|
[Deno 2.x](https://docs.deno.com/runtime/getting_started/installation/)
|
||||||
|
- Configure project
|
||||||
- Clone this repo
|
- Clone this repo
|
||||||
- Install dependencies with `deno i`
|
- Install dependencies with `deno i`
|
||||||
- Compile server with `deno run cross-compile`
|
- Compile server with `deno run cross-compile`
|
||||||
|
|
||||||
## Client Patches
|
## Client Patches
|
||||||
You can configure some client patches from the server. See the IL2CPP universal patch for a list of patch IDs.
|
|
||||||
|
You can configure some client patches from the server. See the IL2CPP universal
|
||||||
|
patch for a list of patch IDs.
|
||||||
<br>Place desired patch ID strings into the config `public.patches`.
|
<br>Place desired patch ID strings into the config `public.patches`.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"tasks": {
|
"tasks": {
|
||||||
"compile-win": "deno compile --target x86_64-pc-windows-msvc -o build/GalvanicCorrosion.exe -A src/main.ts",
|
"compile-win": "deno compile --include res --include src --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",
|
"compile-linux": "deno compile --include res --include src --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",
|
||||||
"dev": "deno run -A src/main.ts --dev"
|
"dev": "deno run -A src/main.ts --dev"
|
||||||
},
|
},
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"validator": "npm:validator@^13.12.0",
|
"validator": "npm:validator@^13.12.0",
|
||||||
"bcrypt": "https://deno.land/x/bcrypt@v0.3.0/mod.ts"
|
"bcrypt": "https://deno.land/x/bcrypt@v0.3.0/mod.ts"
|
||||||
},
|
},
|
||||||
|
"files": [],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"types": [
|
"types": [
|
||||||
"./src/types/express.ts"
|
"./src/types/express.ts"
|
||||||
|
|||||||
36
deno.lock
generated
@@ -8,6 +8,7 @@
|
|||||||
"jsr:@std/crypto@^1.0.3": "1.0.3",
|
"jsr:@std/crypto@^1.0.3": "1.0.3",
|
||||||
"jsr:@std/internal@^1.0.5": "1.0.5",
|
"jsr:@std/internal@^1.0.5": "1.0.5",
|
||||||
"jsr:@std/uuid@*": "1.0.4",
|
"jsr:@std/uuid@*": "1.0.4",
|
||||||
|
"npm:@imagemagick/magick-wasm@0.0.31": "0.0.31",
|
||||||
"npm:@types/cookie-parser@*": "1.4.8_@types+express@5.0.0",
|
"npm:@types/cookie-parser@*": "1.4.8_@types+express@5.0.0",
|
||||||
"npm:@types/cookie-parser@^1.4.8": "1.4.8_@types+express@5.0.0",
|
"npm:@types/cookie-parser@^1.4.8": "1.4.8_@types+express@5.0.0",
|
||||||
"npm:@types/express@*": "5.0.0",
|
"npm:@types/express@*": "5.0.0",
|
||||||
@@ -110,6 +111,9 @@
|
|||||||
"ws"
|
"ws"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"@imagemagick/magick-wasm@0.0.31": {
|
||||||
|
"integrity": "sha512-QNivAUxSaItuiY8ziI/vRy6TtoecD7TOsD1LGZCG3wv8lfbdGbIj2QiJk0FlGkGwAVR966NlD3mkxPNvQrvq0w=="
|
||||||
|
},
|
||||||
"@ioredis/commands@1.2.0": {
|
"@ioredis/commands@1.2.0": {
|
||||||
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
|
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
|
||||||
},
|
},
|
||||||
@@ -708,6 +712,9 @@
|
|||||||
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="
|
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redirects": {
|
||||||
|
"https://deno.land/x/imagemagick_deno/mod.ts": "https://deno.land/x/imagemagick_deno@0.0.31/mod.ts"
|
||||||
|
},
|
||||||
"remote": {
|
"remote": {
|
||||||
"https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74",
|
"https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74",
|
||||||
"https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49",
|
"https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49",
|
||||||
@@ -808,6 +815,35 @@
|
|||||||
"https://deno.land/x/emit@0.25.0/_utils.ts": "98412edc7aa29e77d592b54fbad00bdec1b05d0c25eb772a5f8edc9813e08d88",
|
"https://deno.land/x/emit@0.25.0/_utils.ts": "98412edc7aa29e77d592b54fbad00bdec1b05d0c25eb772a5f8edc9813e08d88",
|
||||||
"https://deno.land/x/emit@0.25.0/emit.generated.js": "0728e0cd293b930db2532f8cb5087fdb77aee1f30a059207533780f40250fd6a",
|
"https://deno.land/x/emit@0.25.0/emit.generated.js": "0728e0cd293b930db2532f8cb5087fdb77aee1f30a059207533780f40250fd6a",
|
||||||
"https://deno.land/x/emit@0.25.0/mod.ts": "66ef8ddaedcfca033eeee851379af59ed3f0e0aa6e025e7cdd24e4e158d874f3",
|
"https://deno.land/x/emit@0.25.0/mod.ts": "66ef8ddaedcfca033eeee851379af59ed3f0e0aa6e025e7cdd24e4e158d874f3",
|
||||||
|
"https://deno.land/x/imagemagick_deno@0.0.31/mod.ts": "124d7f045429f6e6c486b86e72d025410d09576bc0d8075e69f97118a1a33413",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/ImageScript.js": "cf90773c966031edd781ed176c598f7ed495e7694cd9b86c986d2d97f783cca0",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/mod.ts": "18a6cb83c55e690c873505f6fe867364c678afb64934fe7aef593a6b92f79995",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/png/src/crc.mjs": "5cf50de181d61dd00e66a240d811018ba5070afa8bba302f393604404604de84",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/png/src/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/png/src/png.mjs": "96ef0ceff1b5a6cd9304749e5f187b4ab238509fb5f9a8be8ee934240271ed8d",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/png/src/zlib.mjs": "9867dc3fab1d31b664f9344b0d7e977f493d9c912a76c760d012ed2b89f7061c",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/utils/buffer.js": "952cb1beb8827e50a493a5d1f29a4845e8c648789406d389dd51f51205ba02d8",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/utils/crc32.js": "573d6222b3605890714ebc374e687ec2aa3e9a949223ea199483e47ca4864f7d",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/utils/png.js": "fbed9117e0a70602645d70df9c103ff6e79c03e987bd5c1685dcb4200729b6de",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/utils/wasm/font.js": "9e75d842608c057045698d6a7cdf5ffd27241b5cdea0391c89a1917b31294524",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/utils/wasm/gif.js": "8b86f7b96486bb8ff50fbc7c7487f86cb5cef85e6acd71e1def78a1aa2f12e4f",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/utils/wasm/jpeg.js": "75295e2fcf96b4f7bb894b3844fdaa8140d63169d28b466b5d5be89d59a7b6e6",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/utils/wasm/png.js": "0659536a8dd8f892c8346e268b2754b4414fad0ec1e9794dfcde1ba1c804ee02",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/utils/wasm/svg.js": "f5c8a9d1977b51a7c07549ceb6bbbaca9497321a193f28b3dc229a42d91bcf14",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/utils/wasm/tiff.js": "c2d7bdaef094df25aae1752e75167f485e89275d76a1379e39d8949580b7af4f",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/utils/wasm/zlib.js": "749875f83abffe24d3b977475a0cbd5f9b52bee1fbdbef61ec183cbfc17805f6",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/framebuffer.mjs": "add44ff184636659714b3c6d4b896f628545451abffbc30b5bcc2e8d9a73d012",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/ops/blur.mjs": "80716f1ffab8a2aeb54a036f583bf51a2b9dd37e005adc000add803df8e8a12f",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/ops/color.mjs": "5e72cdcbf97dc939a2795223f01e3cb0544c0c56b03ea2aa026050df58348814",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/ops/crop.mjs": "69431fa6f687fd9f0c31eff0ec27d7ac925275005e53a37f0c3fab4cc4d9a9ea",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/ops/fill.mjs": "cf1b9488314753fbc9ebf03410ac74c2a34ea5a69fb6892cd6e8366cd1930d93",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/ops/flip.mjs": "825a34a66567dcf15e76a719f1bf2f66fb106503cd69942292b1b0ae05c5718e",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/ops/index.mjs": "423ba687119be2bba8cec72890577d3afa3621b6b8108912242fe937a183f2aa",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/ops/iterator.mjs": "c2adf3d90ce00719a02c48c97634574176a3501ff026676259bd71aa8f5d69b9",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/ops/overlay.mjs": "7e6e2c2ffd25006d52597ab8babc5f8f503d388a3fdf2fbc0eaea02799a020c9",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/ops/resize.mjs": "814e78ebce8eaf8f1f918688db7b52a141405e06a36ed4b25d04413d69e7d17b",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/ops/rotate.mjs": "a1b65616717bd2eed8db406affea3263b4674dada46b56441ef38167a187455d",
|
||||||
|
"https://deno.land/x/imagescript@1.3.0/v2/util/mem.mjs": "4968d400dae069b4bf0ef4767c1802fd2cc7d15d90eda4cfadf5b4cd19b96c6d",
|
||||||
"https://deno.land/x/leaf@v1.0.4/constants.ts": "2b18c5be5a57cea4d3d6298d7c4c636e5db821c580c3197f9c9bcab65f8c3bf0",
|
"https://deno.land/x/leaf@v1.0.4/constants.ts": "2b18c5be5a57cea4d3d6298d7c4c636e5db821c580c3197f9c9bcab65f8c3bf0",
|
||||||
"https://deno.land/x/leaf@v1.0.4/functions/getFileInMem.ts": "cec6c3c6add22c0c3316d8301994ab583feac5c3052df3072ad12976ea2aeec4",
|
"https://deno.land/x/leaf@v1.0.4/functions/getFileInMem.ts": "cec6c3c6add22c0c3316d8301994ab583feac5c3052df3072ad12976ea2aeec4",
|
||||||
"https://deno.land/x/leaf@v1.0.4/functions/getFilePath.ts": "80ce141c1bd9735d3b7961b6ec8736070475c296c342be6bb4e189483f020801",
|
"https://deno.land/x/leaf@v1.0.4/functions/getFilePath.ts": "80ce141c1bd9735d3b7961b6ec8736070475c296c342be6bb4e189483f020801",
|
||||||
|
|||||||
BIN
res/img/3DCharades.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
res/img/Clearcut.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
res/img/CrimsonCauldron.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
res/img/CyberJunkCity.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
res/img/DefaultProfileImage.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
res/img/DiscGolfLake.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
res/img/DiscGolfPropulsion.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
res/img/Dodgeball.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
res/img/GoldenTrophy.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
res/img/Gym.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
res/img/Hangar.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
res/img/Homestead.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
res/img/IsleOfLostSkulls.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
res/img/Lounge.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
res/img/Paddleball.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
res/img/Paintball.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
res/img/Park.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
res/img/PerformanceHall.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
res/img/PropulsionTestRange.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
res/img/Quarry.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
res/img/RecCenter.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
res/img/RecRoyaleSolos.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
res/img/RecRoyaleSquads.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
res/img/River.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
res/img/Soccer.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
res/img/Spillway.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
res/img/Stadium.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
res/img/TheRiseofJumbotron.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
604
res/rooms.json
@@ -1,605 +1,4 @@
|
|||||||
{
|
[
|
||||||
"Locations": [
|
|
||||||
{
|
|
||||||
"Name": "Dorm Room",
|
|
||||||
"ReplicationId": "76d98498-60a1-430c-ab76-b54a29b7a163",
|
|
||||||
"SceneName": "dormroom2",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 1,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Rec Center",
|
|
||||||
"ReplicationId": "cbad71af-0831-44d8-b8ef-69edafa841f6",
|
|
||||||
"SceneName": "reccenter",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Charades",
|
|
||||||
"ReplicationId": "4078dfed-24bb-4db7-863f-578ba48d726b",
|
|
||||||
"SceneName": "charades",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 5000,
|
|
||||||
"LocationEnum": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Lake",
|
|
||||||
"ReplicationId": "f6f7256c-e438-4299-b99e-d20bef8cf7e0",
|
|
||||||
"SceneName": "discgolf",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 3001,
|
|
||||||
"LocationEnum": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Propulsion",
|
|
||||||
"ReplicationId": "d9378c9f-80bc-46fb-ad1e-1bed8a674f55",
|
|
||||||
"SceneName": "Discgolf_Propulsion",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 3000,
|
|
||||||
"LocationEnum": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Dodgeball",
|
|
||||||
"ReplicationId": "3d474b26-26f7-45e9-9a36-9b02847d5e6f",
|
|
||||||
"SceneName": "dodgeball",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 8000,
|
|
||||||
"LocationEnum": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "The Lounge",
|
|
||||||
"ReplicationId": "a067557f-ca32-43e6-b6e5-daaec60b4f5a",
|
|
||||||
"SceneName": "eventroom",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": false,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Paddleball",
|
|
||||||
"ReplicationId": "d89f74fa-d51e-477a-a425-025a891dd499",
|
|
||||||
"SceneName": "paddleball",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 7000,
|
|
||||||
"LocationEnum": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "River",
|
|
||||||
"ReplicationId": "e122fe98-e7db-49e8-a1b1-105424b6e1f0",
|
|
||||||
"SceneName": "paintball",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 2003,
|
|
||||||
"LocationEnum": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Homestead",
|
|
||||||
"ReplicationId": "a785267d-c579-42ea-be43-fec1992d1ca7",
|
|
||||||
"SceneName": "paintball2_open",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 2001,
|
|
||||||
"LocationEnum": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Quarry",
|
|
||||||
"ReplicationId": "ff4c6427-7079-4f59-b22a-69b089420827",
|
|
||||||
"SceneName": "Paintball_Castle",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 2002,
|
|
||||||
"LocationEnum": 10
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Clear Cut",
|
|
||||||
"ReplicationId": "380d18b5-de9c-49f3-80f7-f4a95c1de161",
|
|
||||||
"SceneName": "Paintball_ClearCut",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 2000,
|
|
||||||
"LocationEnum": 11
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Spillway",
|
|
||||||
"ReplicationId": "58763055-2dfb-4814-80b8-16fac5c85709",
|
|
||||||
"SceneName": "Paintball_Dam",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 2004,
|
|
||||||
"LocationEnum": 12
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Quest For The Golden Trophy",
|
|
||||||
"ReplicationId": "91e16e35-f48f-4700-ab8a-a1b79e50e51b",
|
|
||||||
"SceneName": "Quest_additive",
|
|
||||||
"RequiredSubSceneNames": [
|
|
||||||
"Quest_Foyer"
|
|
||||||
],
|
|
||||||
"LevelRoomSubSceneNames": [
|
|
||||||
"Quest_Armory",
|
|
||||||
"Quest_Hallway1",
|
|
||||||
"Quest_Hallway2",
|
|
||||||
"Quest_Hallway3",
|
|
||||||
"Quest_DeadIsland",
|
|
||||||
"Quest_Hallway4",
|
|
||||||
"Quest_Library",
|
|
||||||
"Quest_Hallway5",
|
|
||||||
"Quest_Battlefield",
|
|
||||||
"Quest_Cafeteria"
|
|
||||||
],
|
|
||||||
"MaxPlayers": 4,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 13
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "The Rise Of JumboTron",
|
|
||||||
"ReplicationId": "acc06e66-c2d0-4361-b0cd-46246a4c455c",
|
|
||||||
"SceneName": "Quest_Scifi_Additive",
|
|
||||||
"RequiredSubSceneNames": [
|
|
||||||
"Quest_Scifi_Foyer"
|
|
||||||
],
|
|
||||||
"LevelRoomSubSceneNames": [
|
|
||||||
"Quest_Scifi_Armory",
|
|
||||||
"Quest_Scifi_Battlefield1",
|
|
||||||
"Quest_Scifi_Hallway1",
|
|
||||||
"Quest_Scifi_Hallway2",
|
|
||||||
"Quest_Scifi_Reception",
|
|
||||||
"Quest_Scifi_StadiumConcession",
|
|
||||||
"Quest_Scifi_StadiumEntry",
|
|
||||||
"Quest_Scifi_Garage",
|
|
||||||
"Quest_Scifi_Cargo1",
|
|
||||||
"Quest_Scifi_Stadium"
|
|
||||||
],
|
|
||||||
"MaxPlayers": 4,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 14
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Curse of the Crimson Cauldron",
|
|
||||||
"ReplicationId": "949fa41f-4347-45c0-b7ac-489129174045",
|
|
||||||
"SceneName": "Quest_Goblin2_additive",
|
|
||||||
"RequiredSubSceneNames": [
|
|
||||||
"Quest_Goblin2_Foyer"
|
|
||||||
],
|
|
||||||
"LevelRoomSubSceneNames": [
|
|
||||||
"Quest_Goblin2_Armory",
|
|
||||||
"Quest_Goblin2_CastleCourtyard",
|
|
||||||
"Quest_Goblin2_Forest1",
|
|
||||||
"Quest_Goblin2_GoblinCamp",
|
|
||||||
"Quest_Goblin2_Forest2",
|
|
||||||
"Quest_Goblin2_Bog",
|
|
||||||
"Quest_Goblin2_Mines1",
|
|
||||||
"Quest_Goblin2_BoilerRoom",
|
|
||||||
"Quest_Goblin2_BellTowerStairs",
|
|
||||||
"Quest_Goblin2_BellTowerArena"
|
|
||||||
],
|
|
||||||
"MaxPlayers": 4,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 15
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "The Isle of Lost Skulls",
|
|
||||||
"ReplicationId": "7e01cfe0-820a-406f-b1b3-0a5bf575235c",
|
|
||||||
"SceneName": "Quest_Pirate1_additive",
|
|
||||||
"RequiredSubSceneNames": [
|
|
||||||
"Quest_Pirate1_Foyer"
|
|
||||||
],
|
|
||||||
"LevelRoomSubSceneNames": [
|
|
||||||
"Quest_Pirate1_Hallway1",
|
|
||||||
"Quest_Pirate1_Hallway2",
|
|
||||||
"Quest_Pirate1_ShipDeck",
|
|
||||||
"Quest_Pirate1_Beach1",
|
|
||||||
"Quest_Pirate1_Beach2",
|
|
||||||
"Quest_Pirate1_Caves1",
|
|
||||||
"Quest_Pirate1_SunkenShip",
|
|
||||||
"Quest_Pirate1_BossArena"
|
|
||||||
],
|
|
||||||
"MaxPlayers": 3,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 16
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Soccer",
|
|
||||||
"ReplicationId": "6d5eea4b-f069-4ed0-9916-0e2f07df0d03",
|
|
||||||
"SceneName": "soccer",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 6000,
|
|
||||||
"LocationEnum": 17
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Art Testing",
|
|
||||||
"ReplicationId": "42699ed2-0c1b-4f3d-93a2-ce01dfce7a79",
|
|
||||||
"SceneName": "ArtTesting",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 1,
|
|
||||||
"IsEditorOnly": true,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 18
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Performance Hall",
|
|
||||||
"ReplicationId": "9932f88f-3929-43a0-a012-a40b5128e346",
|
|
||||||
"SceneName": "PerformanceHall",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 40,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "PSVR Room Calibration",
|
|
||||||
"ReplicationId": "f5fbd9c9-e853-4036-9d48-5f68e861af04",
|
|
||||||
"SceneName": "room_calibration",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 1,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 20
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Park",
|
|
||||||
"ReplicationId": "0a864c86-5a71-4e18-8041-8124e4dc9d98",
|
|
||||||
"SceneName": "Park",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": false,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 21
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Warehouse",
|
|
||||||
"ReplicationId": "239e676c-f12f-489f-bf3a-d4c383d692c3",
|
|
||||||
"SceneName": "Arena_Hangar3",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": 0,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": true,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.6,
|
|
||||||
"CustomTeamColors": [
|
|
||||||
{
|
|
||||||
"Team": 0,
|
|
||||||
"Color": {
|
|
||||||
"r": 0.38039216,
|
|
||||||
"g": 1.0,
|
|
||||||
"b": 1.0,
|
|
||||||
"a": 1.0
|
|
||||||
},
|
|
||||||
"AlternateColor": {
|
|
||||||
"r": 0.0,
|
|
||||||
"g": 0.1882353,
|
|
||||||
"b": 1.0,
|
|
||||||
"a": 1.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Team": 1,
|
|
||||||
"Color": {
|
|
||||||
"r": 1.0,
|
|
||||||
"g": 0.11764706,
|
|
||||||
"b": 0.41960785,
|
|
||||||
"a": 1.0
|
|
||||||
},
|
|
||||||
"AlternateColor": {
|
|
||||||
"r": 1.0,
|
|
||||||
"g": 0.0,
|
|
||||||
"b": 0.15686275,
|
|
||||||
"a": 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 22
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "CyberJunk City",
|
|
||||||
"ReplicationId": "9d6456ce-6264-48b4-808d-2d96b3d91038",
|
|
||||||
"SceneName": "Arena_Cyberjunk_City",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 20,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": 0,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": true,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.6,
|
|
||||||
"CustomTeamColors": [
|
|
||||||
{
|
|
||||||
"Team": 1,
|
|
||||||
"Color": {
|
|
||||||
"r": 1.0,
|
|
||||||
"g": 0.58431375,
|
|
||||||
"b": 0.15294118,
|
|
||||||
"a": 1.0
|
|
||||||
},
|
|
||||||
"AlternateColor": {
|
|
||||||
"r": 1.0,
|
|
||||||
"g": 0.0,
|
|
||||||
"b": 0.0,
|
|
||||||
"a": 1.0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Team": 0,
|
|
||||||
"Color": {
|
|
||||||
"r": 0.18431373,
|
|
||||||
"g": 1.0,
|
|
||||||
"b": 0.5921569,
|
|
||||||
"a": 1.0
|
|
||||||
},
|
|
||||||
"AlternateColor": {
|
|
||||||
"r": 0.0,
|
|
||||||
"g": 0.41960785,
|
|
||||||
"b": 1.0,
|
|
||||||
"a": 1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 23
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Maker Room",
|
|
||||||
"ReplicationId": "a75f7547-79eb-47c6-8986-6767abcb4f92",
|
|
||||||
"SceneName": "Basement",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 40,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": false,
|
|
||||||
"SupportedGameMode": -1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 24
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Frontier Squads",
|
|
||||||
"ReplicationId": "253fa009-6e65-4c90-91a1-7137a56a267f",
|
|
||||||
"SceneName": "RecRoyale_Frontier",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 18,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": 1,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 25
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Name": "Frontier Solos",
|
|
||||||
"ReplicationId": "b010171f-4875-4e89-baba-61e878cd41e1",
|
|
||||||
"SceneName": "RecRoyale_Frontier",
|
|
||||||
"RequiredSubSceneNames": [],
|
|
||||||
"LevelRoomSubSceneNames": [],
|
|
||||||
"MaxPlayers": 16,
|
|
||||||
"IsEditorOnly": false,
|
|
||||||
"EmptyOnSandboxClone": true,
|
|
||||||
"SupportedGameMode": 0,
|
|
||||||
"GameTeamColorSettings": {
|
|
||||||
"TeamOutfitColorEmissionEnabled": false,
|
|
||||||
"TeamOutfitColorEmissionAmount": 0.0,
|
|
||||||
"CustomTeamColors": []
|
|
||||||
},
|
|
||||||
"GiftContext": 0,
|
|
||||||
"LocationEnum": 26
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Rooms": [
|
|
||||||
{
|
{
|
||||||
"Name": "Calibration",
|
"Name": "Calibration",
|
||||||
"ReplicationId": "30040e05-b7b9-9f44-eb08-b9f154d2ecfc",
|
"ReplicationId": "30040e05-b7b9-9f44-eb08-b9f154d2ecfc",
|
||||||
@@ -1622,4 +1021,3 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
|
||||||
152
src/apiutils.ts
@@ -4,27 +4,29 @@ import Logging from "@proxnet/undead-logging";
|
|||||||
import { decode } from "@gz/jwt";
|
import { decode } from "@gz/jwt";
|
||||||
import { Config } from "./config.ts";
|
import { Config } from "./config.ts";
|
||||||
import { AuthType, User, UserTokenFormat } from "./data/users.ts";
|
import { AuthType, User, UserTokenFormat } from "./data/users.ts";
|
||||||
|
import Profile, { ProfileTokenFormat } from "./data/profiles.ts";
|
||||||
|
|
||||||
const config = Config.getConfig();
|
const config = Config.getConfig();
|
||||||
|
|
||||||
const log = new Logging('APIUtils');
|
const log = new Logging("APIUtils");
|
||||||
|
|
||||||
type AppRouter = {
|
type AppRouter = {
|
||||||
path: string,
|
path: string;
|
||||||
router: express.Router
|
router: express.Router;
|
||||||
}
|
};
|
||||||
|
|
||||||
export function createRouter(path: string) {
|
export function createRouter(path: string) {
|
||||||
const router: AppRouter = {
|
const router: AppRouter = {
|
||||||
path: path,
|
path: path,
|
||||||
router: express.Router()
|
router: express.Router(),
|
||||||
}
|
};
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateRandomString(length: number) {
|
export function generateRandomString(length: number) {
|
||||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
const characters =
|
||||||
let randomString = '';
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
let randomString = "";
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||||
@@ -35,11 +37,20 @@ export function generateRandomString(length: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function checkQueryTypes<T>(typeDef: T) {
|
export function checkQueryTypes<T>(typeDef: T) {
|
||||||
return (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
|
return (
|
||||||
|
rq: express.Request,
|
||||||
|
rs: express.Response,
|
||||||
|
nxt: express.NextFunction,
|
||||||
|
) => {
|
||||||
for (const key in typeDef) {
|
for (const key in typeDef) {
|
||||||
if (typeof rq.query[key] !== typeof (typeDef)[key]) {
|
if (typeof rq.query[key] !== typeof typeDef[key]) {
|
||||||
rs.statusCode = 400;
|
rs.statusCode = 400;
|
||||||
rs.json(genericResponseFormat(true, "One or more query parameters were invalid or not found."));
|
rs.json(
|
||||||
|
genericResponseFormat(
|
||||||
|
true,
|
||||||
|
"One or more query parameters were invalid or not found.",
|
||||||
|
),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,12 +58,21 @@ export function checkQueryTypes<T>(typeDef: T) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
export function checkBodyTypes<T>(typeDef: T) {
|
export function checkBodyTypes<T>(typeDef: T) {
|
||||||
return (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
|
return (
|
||||||
|
rq: express.Request,
|
||||||
|
rs: express.Response,
|
||||||
|
nxt: express.NextFunction,
|
||||||
|
) => {
|
||||||
for (const key in typeDef) {
|
for (const key in typeDef) {
|
||||||
if (typeof rq.body[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.`);
|
||||||
rs.statusCode = 400;
|
rs.statusCode = 400;
|
||||||
rs.json(genericResponseFormat(true, "One or more body values were invalid or not found."));
|
rs.json(
|
||||||
|
genericResponseFormat(
|
||||||
|
true,
|
||||||
|
"One or more body values were invalid or not found.",
|
||||||
|
),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,28 +80,40 @@ export function checkBodyTypes<T>(typeDef: T) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function genericResponseFormat(failure: boolean, msg: string | null = null, data: object | null = null) {
|
export function genericResponseFormat(
|
||||||
|
failure: boolean,
|
||||||
|
msg: string | null = null,
|
||||||
|
data: object | null = null,
|
||||||
|
) {
|
||||||
return { failed: failure, message: msg, data: data };
|
return { failed: failure, message: msg, data: data };
|
||||||
}
|
}
|
||||||
export function genericResponse(failure: boolean, msg: string | null = null, data: object | null = null) {
|
export function genericResponse(
|
||||||
|
failure: boolean,
|
||||||
|
msg: string | null = null,
|
||||||
|
data: object | null = null,
|
||||||
|
) {
|
||||||
return (_rq: express.Request, rs: express.Response) => {
|
return (_rq: express.Request, rs: express.Response) => {
|
||||||
rs.json({ failed: failure, message: msg, data: data });
|
rs.json({ failed: failure, message: msg, data: data });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
type RecNetResponse = {
|
type RecNetResponse = {
|
||||||
Success: boolean,
|
Success: boolean;
|
||||||
Message: string
|
Message: string;
|
||||||
};
|
};
|
||||||
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 (_rq: express.Request, rs: express.Response) => {
|
return (_rq: express.Request, rs: express.Response) => {
|
||||||
rs.json(msg);
|
rs.json(msg);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function logBody(rq: express.Request, _rs: express.Response, nxt: express.NextFunction) {
|
export function logBody(
|
||||||
nxt();
|
rq: express.Request,
|
||||||
|
_rs: express.Response,
|
||||||
|
nxt: express.NextFunction,
|
||||||
|
) {
|
||||||
log.d(`Request body: ${JSON.stringify(rq.body)}`);
|
log.d(`Request body: ${JSON.stringify(rq.body)}`);
|
||||||
|
nxt();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emptyArrayResponse(_rq: express.Request, rs: express.Response) {
|
export function emptyArrayResponse(_rq: express.Request, rs: express.Response) {
|
||||||
@@ -89,27 +121,26 @@ export function emptyArrayResponse(_rq: express.Request, rs: express.Response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getSrcIpDefault(rq: express.Request) {
|
export function getSrcIpDefault(rq: express.Request) {
|
||||||
const cfIp = rq.header('cf-connecting-ip');
|
const cfIp = rq.header("cf-connecting-ip");
|
||||||
if (cfIp !== undefined) return cfIp;
|
if (cfIp !== undefined) return cfIp;
|
||||||
|
|
||||||
const xrIp = rq.header('x-real-ip');
|
const xrIp = rq.header("x-real-ip");
|
||||||
if (xrIp !== undefined) return xrIp;
|
if (xrIp !== undefined) return xrIp;
|
||||||
|
|
||||||
const ip = typeof rq.ip === 'undefined' ? '(unknown source)' : rq.ip;
|
const ip = typeof rq.ip === "undefined" ? "(unknown source)" : rq.ip;
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function statusResponse(code: number) {
|
export function statusResponse(code: number) {
|
||||||
return (_rq: express.Request, rs: express.Response) => {
|
return (_rq: express.Request, rs: express.Response) => {
|
||||||
rs.sendStatus(code);
|
rs.sendStatus(code);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RateLimiter {
|
export class RateLimiter {
|
||||||
|
#intervalId: number;
|
||||||
|
|
||||||
#intervalId: number
|
#hitLimit: number;
|
||||||
|
|
||||||
#hitLimit: number
|
|
||||||
|
|
||||||
#addressHits: Map<string, number> = new Map();
|
#addressHits: Map<string, number> = new Map();
|
||||||
|
|
||||||
@@ -118,17 +149,15 @@ export class RateLimiter {
|
|||||||
* @param limit Number of hits (inclusive) before requests are blocked
|
* @param limit Number of hits (inclusive) before requests are blocked
|
||||||
*/
|
*/
|
||||||
constructor(interval: number, limit: number) {
|
constructor(interval: number, limit: number) {
|
||||||
|
|
||||||
this.#hitLimit = limit;
|
this.#hitLimit = limit;
|
||||||
|
|
||||||
this.#intervalId = setInterval(() => {
|
this.#intervalId = setInterval(() => {
|
||||||
this.#addressHits.clear();
|
this.#addressHits.clear();
|
||||||
}, interval * 1000);
|
}, interval * 1000);
|
||||||
|
|
||||||
Deno.addSignalListener('SIGINT', () => {
|
Deno.addSignalListener("SIGINT", () => {
|
||||||
this.#close();
|
this.#close();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#addressIncrement(address: string) {
|
#addressIncrement(address: string) {
|
||||||
@@ -147,66 +176,89 @@ export class RateLimiter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
middle() {
|
middle() {
|
||||||
|
return (
|
||||||
return (rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
|
rq: express.Request,
|
||||||
|
rs: express.Response,
|
||||||
|
nxt: express.NextFunction,
|
||||||
|
) => {
|
||||||
const address = getSrcIpDefault(rq);
|
const address = getSrcIpDefault(rq);
|
||||||
this.#addressIncrement(address);
|
this.#addressIncrement(address);
|
||||||
|
|
||||||
const hits = this.#getAddressHits(address);
|
const hits = this.#getAddressHits(address);
|
||||||
if (hits && hits > this.#hitLimit) {
|
if (hits && hits > this.#hitLimit) {
|
||||||
rs.statusCode = 429;
|
rs.statusCode = 429;
|
||||||
rs.json(genericResponseFormat(true, `Rate limit for address ${address} reached. Try again in a moment.`));
|
rs.json(
|
||||||
|
genericResponseFormat(
|
||||||
|
true,
|
||||||
|
`Rate limit for address ${address} reached. Try again in a moment.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
} else nxt();
|
} else nxt();
|
||||||
}
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#close() {
|
#close() {
|
||||||
clearInterval(this.#intervalId);
|
clearInterval(this.#intervalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function UserAuthentication(rq: express.Request, rs: express.Response, nxt: express.NextFunction) {
|
export interface TokenBaseFormat {
|
||||||
|
typ: AuthType;
|
||||||
|
iss: string;
|
||||||
|
nbf: number;
|
||||||
|
exp: number;
|
||||||
|
iat: number;
|
||||||
|
}
|
||||||
|
export type TokenFormat = UserTokenFormat | ProfileTokenFormat;
|
||||||
|
|
||||||
|
export async function Authentication(
|
||||||
|
rq: express.Request,
|
||||||
|
rs: express.Response,
|
||||||
|
nxt: express.NextFunction,
|
||||||
|
) {
|
||||||
function returnUnauthorized() {
|
function returnUnauthorized() {
|
||||||
rs.statusCode = 401;
|
rs.statusCode = 401;
|
||||||
rs.json(genericResponseFormat(true, 'Authorization required.'));
|
rs.json(genericResponseFormat(true, "Authorization required."));
|
||||||
}
|
}
|
||||||
|
|
||||||
const token: string | undefined = rq.header('GalvanicAuth');
|
const token: string | undefined = rq.header("GalvanicAuth");
|
||||||
if (typeof token == 'undefined') {
|
if (typeof token == "undefined") {
|
||||||
returnUnauthorized();
|
returnUnauthorized();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const decodedToken = await decode<UserTokenFormat>(token, config.auth.secret, { algorithm: "HS512" });
|
const decodedToken = await decode<TokenFormat>(
|
||||||
|
token,
|
||||||
|
config.auth.secret,
|
||||||
|
{
|
||||||
|
algorithm: "HS512",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const valid = ![
|
const valid = ![
|
||||||
decodedToken.iss == config.web.publichost,
|
decodedToken.iss == config.web.publichost,
|
||||||
decodedToken.nbf < Math.round(Date.now() / 1000),
|
decodedToken.nbf < Math.round(Date.now() / 1000),
|
||||||
decodedToken.exp > Math.round(Date.now() / 1000),
|
decodedToken.exp > Math.round(Date.now() / 1000),
|
||||||
decodedToken.typ == AuthType.Web
|
|
||||||
].includes(false);
|
].includes(false);
|
||||||
if (valid) {
|
if (valid) {
|
||||||
|
if (decodedToken.typ == AuthType.Web) {
|
||||||
rs.locals.user = new User(decodedToken.sub);
|
rs.locals.user = new User(decodedToken.sub);
|
||||||
nxt();
|
} else if (decodedToken.typ == AuthType.Game) {
|
||||||
|
rs.locals.profile = new Profile(decodedToken.sub);
|
||||||
}
|
}
|
||||||
else {
|
nxt();
|
||||||
|
} else {
|
||||||
returnUnauthorized();
|
returnUnauthorized();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
returnUnauthorized();
|
returnUnauthorized();
|
||||||
log.w(`User Authentication failed: ${err}`);
|
log.w(`User Authentication failed: ${err}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NoBody = Record<string | number | symbol, never>
|
export type NoBody = Record<string | number | symbol, never>;
|
||||||
|
|
||||||
export * as APIUtils from "./apiutils.ts"
|
export * as APIUtils from "./apiutils.ts";
|
||||||
|
|||||||
@@ -4,58 +4,58 @@ import * as fs from "node:fs";
|
|||||||
const log = new Logging("Config");
|
const log = new Logging("Config");
|
||||||
|
|
||||||
type RedisConfiguration = {
|
type RedisConfiguration = {
|
||||||
host: string,
|
host: string;
|
||||||
port: number,
|
port: number;
|
||||||
username: string,
|
username: string;
|
||||||
password: string,
|
password: string;
|
||||||
db: number
|
db: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
type WebConfiguration = {
|
type WebConfiguration = {
|
||||||
port: number,
|
port: number;
|
||||||
host: string,
|
host: string;
|
||||||
publichost: string,
|
publichost: string;
|
||||||
securepublichost: boolean
|
securepublichost: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
type PublicConfiguration = {
|
type PublicConfiguration = {
|
||||||
serverName: string,
|
serverName: string;
|
||||||
serverId: string,
|
serverId: string;
|
||||||
owner: string,
|
owner: string;
|
||||||
motd: string,
|
motd: string;
|
||||||
levelScale: number,
|
levelScale: number;
|
||||||
maxLevels: number
|
maxLevels: number;
|
||||||
patches: string[],
|
patches: string[];
|
||||||
}
|
};
|
||||||
|
|
||||||
type LoggingConfiguration = {
|
type LoggingConfiguration = {
|
||||||
notfound: boolean,
|
notfound: boolean;
|
||||||
debug: boolean,
|
debug: boolean;
|
||||||
network: boolean
|
network: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
type DiscordConfiguration = {
|
type DiscordConfiguration = {
|
||||||
token: string,
|
token: string;
|
||||||
clientId: string,
|
clientId: string;
|
||||||
guildId: string
|
guildId: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
type AuthConfiguration = {
|
type AuthConfiguration = {
|
||||||
secret: string,
|
secret: string;
|
||||||
/**
|
/**
|
||||||
* In Hours
|
* In Hours
|
||||||
*/
|
*/
|
||||||
timeout: number
|
timeout: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type GalvanicConfiguration = {
|
export type GalvanicConfiguration = {
|
||||||
redis: RedisConfiguration,
|
redis: RedisConfiguration;
|
||||||
web: WebConfiguration,
|
web: WebConfiguration;
|
||||||
public: PublicConfiguration,
|
public: PublicConfiguration;
|
||||||
logging: LoggingConfiguration,
|
logging: LoggingConfiguration;
|
||||||
discord: DiscordConfiguration | null,
|
discord: DiscordConfiguration | null;
|
||||||
auth: AuthConfiguration
|
auth: AuthConfiguration;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const defaultConfig: GalvanicConfiguration = {
|
export const defaultConfig: GalvanicConfiguration = {
|
||||||
redis: {
|
redis: {
|
||||||
@@ -63,7 +63,7 @@ export const defaultConfig: GalvanicConfiguration = {
|
|||||||
port: 6379,
|
port: 6379,
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
db: 0
|
db: 0,
|
||||||
},
|
},
|
||||||
web: {
|
web: {
|
||||||
port: 3000,
|
port: 3000,
|
||||||
@@ -83,20 +83,20 @@ export const defaultConfig: GalvanicConfiguration = {
|
|||||||
logging: {
|
logging: {
|
||||||
notfound: false,
|
notfound: false,
|
||||||
debug: false,
|
debug: false,
|
||||||
network: false
|
network: false,
|
||||||
},
|
},
|
||||||
discord: null,
|
discord: null,
|
||||||
auth: {
|
auth: {
|
||||||
secret: "CHANGE-ME-PLEASE",
|
secret: "CHANGE-ME-PLEASE",
|
||||||
timeout: 48
|
timeout: 48,
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
/** The current configuration. Read and parsed only during startup. */
|
/** The current configuration. Read and parsed only during startup. */
|
||||||
let config: GalvanicConfiguration;
|
let config: GalvanicConfiguration;
|
||||||
try {
|
try {
|
||||||
if (!configurationExists()) generateDefaultConfig();
|
if (!configurationExists()) generateDefaultConfig();
|
||||||
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}`);
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
@@ -104,12 +104,15 @@ try {
|
|||||||
|
|
||||||
/** Does the configuration file exist on the disk? */
|
/** Does the configuration file exist on the disk? */
|
||||||
export function configurationExists() {
|
export function configurationExists() {
|
||||||
return fs.existsSync('./config.json');
|
return fs.existsSync("./config.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Place [or overwrite] the [existing] default configuration in the current directory */
|
/** Place [or overwrite] the [existing] default configuration in the current directory */
|
||||||
export function generateDefaultConfig() {
|
export function generateDefaultConfig() {
|
||||||
fs.writeFileSync('./config.json', JSON.stringify(defaultConfig, undefined, ' '));
|
fs.writeFileSync(
|
||||||
|
"./config.json",
|
||||||
|
JSON.stringify(defaultConfig, undefined, " "),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get current server configuration */
|
/** Get current server configuration */
|
||||||
@@ -117,6 +120,6 @@ export function getConfig() {
|
|||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const devMode = Deno.args.includes('--dev');
|
export const devMode = Deno.args.includes("--dev");
|
||||||
|
|
||||||
export * as Config from './config.ts';
|
export * as Config from "./config.ts";
|
||||||
|
|||||||
@@ -3,42 +3,46 @@ import { Redis } from "../db.ts";
|
|||||||
import { Objectives } from "./objectives.ts";
|
import { Objectives } from "./objectives.ts";
|
||||||
|
|
||||||
export type Config = {
|
export type Config = {
|
||||||
Key: string,
|
Key: string;
|
||||||
Value: string
|
Value: string;
|
||||||
}
|
};
|
||||||
export type LevelProgressionItem = {
|
export type LevelProgressionItem = {
|
||||||
Level: number,
|
Level: number;
|
||||||
RequiredXp: number
|
RequiredXp: number;
|
||||||
}
|
};
|
||||||
export type PublicConfig = {
|
export type PublicConfig = {
|
||||||
ServerMaintenance: {
|
ServerMaintenance: {
|
||||||
StartsInMinutes: number
|
StartsInMinutes: number;
|
||||||
},
|
};
|
||||||
LevelProgressionMaps: LevelProgressionItem[],
|
LevelProgressionMaps: LevelProgressionItem[];
|
||||||
DailyObjectives: Objectives.Objective[][],
|
DailyObjectives: Objectives.Objective[][];
|
||||||
ConfigTable: Config[]
|
ConfigTable: Config[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export function getConfig() {
|
export function getConfig() {
|
||||||
const c = Config.getConfig();
|
const c = Config.getConfig();
|
||||||
if (typeof c == 'undefined') return null;
|
if (typeof c == "undefined") return null;
|
||||||
const config = c as Config.GalvanicConfiguration;
|
const config = c as Config.GalvanicConfiguration;
|
||||||
|
|
||||||
function generateLevelProgressionMap() {
|
function generateLevelProgressionMap() {
|
||||||
const m: LevelProgressionItem[] = [];
|
const m: LevelProgressionItem[] = [];
|
||||||
for (let i = 0; i < config.public.maxLevels + 1; i++)
|
for (let i = 0; i < config.public.maxLevels + 1; i++) {
|
||||||
m.push({Level: i, RequiredXp: Math.round(i * config.public.levelScale * 20)});
|
m.push({
|
||||||
|
Level: i,
|
||||||
|
RequiredXp: Math.round(i * config.public.levelScale * 20),
|
||||||
|
});
|
||||||
|
}
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conf: PublicConfig = {
|
const conf: PublicConfig = {
|
||||||
ServerMaintenance: {
|
ServerMaintenance: {
|
||||||
StartsInMinutes: 0
|
StartsInMinutes: 0,
|
||||||
},
|
},
|
||||||
LevelProgressionMaps: generateLevelProgressionMap(),
|
LevelProgressionMaps: generateLevelProgressionMap(),
|
||||||
DailyObjectives: [],
|
DailyObjectives: [],
|
||||||
ConfigTable: []
|
ConfigTable: [],
|
||||||
}
|
};
|
||||||
|
|
||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
@@ -46,10 +50,16 @@ export function getConfig() {
|
|||||||
export async function getAllGameConfigs() {
|
export async function getAllGameConfigs() {
|
||||||
try {
|
try {
|
||||||
const gameConfigs = new Map<string, string>();
|
const gameConfigs = new Map<string, string>();
|
||||||
const val = await Redis.Database.hgetall(Redis.buildKey(Redis.KeyGroups.Config.Root, Redis.KeyGroups.Config.Game));
|
const val = await Redis.Database.hgetall(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Config.Root,
|
||||||
|
Redis.KeyGroups.Config.Game,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
for (const key of Object.keys(val))
|
for (const key of Object.keys(val)) {
|
||||||
gameConfigs.set(key, val[key]);
|
gameConfigs.set(key, val[key]);
|
||||||
|
}
|
||||||
|
|
||||||
return gameConfigs;
|
return gameConfigs;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -59,10 +69,23 @@ export async function getAllGameConfigs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setGameConfig(key: string, value: string) {
|
export function setGameConfig(key: string, value: string) {
|
||||||
return Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Config.Root, Redis.KeyGroups.Config.Game), key, value);
|
return Redis.Database.hset(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Config.Root,
|
||||||
|
Redis.KeyGroups.Config.Game,
|
||||||
|
),
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
export function getGameConfig(key: string) {
|
export function getGameConfig(key: string) {
|
||||||
return Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Config.Root, Redis.KeyGroups.Config.Game), key);
|
return Redis.Database.hget(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Config.Root,
|
||||||
|
Redis.KeyGroups.Config.Game,
|
||||||
|
),
|
||||||
|
key,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export * as GameConfigs from "./config.ts";
|
export * as GameConfigs from "./config.ts";
|
||||||
@@ -13,5 +13,5 @@ enum AvatarItemType {
|
|||||||
Wrist = 200,
|
Wrist = 200,
|
||||||
Glove,
|
Glove,
|
||||||
Watch,
|
Watch,
|
||||||
TeamWrist
|
TeamWrist,
|
||||||
}
|
}
|
||||||
15
src/data/content/baseimages.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
const base = Deno.mainModule.substring(8, Deno.mainModule.length - 11);
|
||||||
|
|
||||||
|
export function getBaseImage(name: string) {
|
||||||
|
try {
|
||||||
|
return Deno.readFileSync(`${base}/res/img/${name}`);
|
||||||
|
} catch (_err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function getAllBaseImages() {
|
||||||
|
return Array.from(
|
||||||
|
Deno.readDirSync(`${base}/res/img/`).map((val) => val.isFile ? val.name : undefined),
|
||||||
|
).filter((val) => typeof val == "string");
|
||||||
|
}
|
||||||
|
// todo: make this async
|
||||||
@@ -5,7 +5,7 @@ export enum Consumable {
|
|||||||
CHOCOLATE_FROSTED_DONUTS,
|
CHOCOLATE_FROSTED_DONUTS,
|
||||||
CHEESE_PIZZA,
|
CHEESE_PIZZA,
|
||||||
PEPPERONI_PIZZA,
|
PEPPERONI_PIZZA,
|
||||||
GLAZED_DONUTS
|
GLAZED_DONUTS,
|
||||||
}
|
}
|
||||||
|
|
||||||
const ids = [
|
const ids = [
|
||||||
@@ -15,26 +15,21 @@ const ids = [
|
|||||||
"mMCGPgK3tki5S_15q2Z81A",
|
"mMCGPgK3tki5S_15q2Z81A",
|
||||||
"5hIAZ9wg5EyG1cILf4FS2A",
|
"5hIAZ9wg5EyG1cILf4FS2A",
|
||||||
"mq23W-RSP0G8iGNLdrcpUw",
|
"mq23W-RSP0G8iGNLdrcpUw",
|
||||||
"7OZ5AE3uuUyqa0P-2W1ptg"
|
"7OZ5AE3uuUyqa0P-2W1ptg",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export class ConsumableSelection {
|
export class ConsumableSelection {
|
||||||
|
|
||||||
type: Consumable;
|
type: Consumable;
|
||||||
|
|
||||||
guid: string;
|
guid: string;
|
||||||
|
|
||||||
constructor(type: Consumable) {
|
constructor(type: Consumable) {
|
||||||
|
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.guid = ids[type];
|
this.guid = ids[type];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConsumableBuilder {
|
export class ConsumableBuilder {
|
||||||
|
|
||||||
Id: number;
|
Id: number;
|
||||||
|
|
||||||
ConsumableItemDesc: string;
|
ConsumableItemDesc: string;
|
||||||
@@ -47,7 +42,13 @@ export class ConsumableBuilder {
|
|||||||
|
|
||||||
IsActive: boolean;
|
IsActive: boolean;
|
||||||
|
|
||||||
constructor(selection: ConsumableSelection, id: number, createdAt: Date, count: number, active: boolean) {
|
constructor(
|
||||||
|
selection: ConsumableSelection,
|
||||||
|
id: number,
|
||||||
|
createdAt: Date,
|
||||||
|
count: number,
|
||||||
|
active: boolean,
|
||||||
|
) {
|
||||||
this.Id = id;
|
this.Id = id;
|
||||||
this.ConsumableItemDesc = selection.guid;
|
this.ConsumableItemDesc = selection.guid;
|
||||||
this.CreatedAt = createdAt.toUTCString();
|
this.CreatedAt = createdAt.toUTCString();
|
||||||
@@ -55,5 +56,4 @@ export class ConsumableBuilder {
|
|||||||
this.UnlockedLevel = 0; // All players have access to every consumable - avatars and equipment are different
|
this.UnlockedLevel = 0; // All players have access to every consumable - avatars and equipment are different
|
||||||
this.IsActive = active;
|
this.IsActive = active;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
36
src/data/content/gamerooms.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
interface InternalScene {
|
||||||
|
Name: string;
|
||||||
|
ReplicationId: string;
|
||||||
|
RoomSceneLocationId: string;
|
||||||
|
IsSandbox: boolean;
|
||||||
|
CanMatchmakeInto: boolean;
|
||||||
|
SupportsJoinInProgress: boolean;
|
||||||
|
UseLevelBasedMatchmaking: boolean;
|
||||||
|
UseAgeBasedMatchmaking: boolean;
|
||||||
|
MaxPlayers: number;
|
||||||
|
}
|
||||||
|
interface InternalRoom {
|
||||||
|
Name: string;
|
||||||
|
ReplicationId: string;
|
||||||
|
Description: string;
|
||||||
|
Accessibility: number;
|
||||||
|
SupportsLevelVoting: boolean;
|
||||||
|
CloningAllowed: boolean;
|
||||||
|
SupportsScreens: boolean;
|
||||||
|
SupportsWalkVR: boolean;
|
||||||
|
SupportsTeleportVR: boolean;
|
||||||
|
Scenes: InternalScene[];
|
||||||
|
}
|
||||||
|
const rooms: InternalRoom[] = JSON.parse(
|
||||||
|
Deno.readTextFileSync(import.meta.dirname + "/res/rooms.json"),
|
||||||
|
);
|
||||||
|
|
||||||
|
export function getInternalRoom(name: string) {
|
||||||
|
return rooms.find((val) => val.Name == name);
|
||||||
|
}
|
||||||
|
export function getAllInternalRooms() {
|
||||||
|
return rooms;
|
||||||
|
}
|
||||||
|
export function getAllInternalRoomNames() {
|
||||||
|
return rooms.map((val) => val.Name);
|
||||||
|
}
|
||||||
24
src/data/content/images.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Buffer } from "node:buffer";
|
||||||
|
import { Redis } from "../../db.ts";
|
||||||
|
|
||||||
|
export async function getImage(filename: string) {
|
||||||
|
const data = await Redis.Database.get(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Content.Root,
|
||||||
|
Redis.KeyGroups.Content.Images,
|
||||||
|
filename,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (data == null) return null;
|
||||||
|
else return Buffer.from(data);
|
||||||
|
}
|
||||||
|
export async function setImage(filename: string, data: Uint8Array) {
|
||||||
|
await Redis.Database.set(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Content.Root,
|
||||||
|
Redis.KeyGroups.Content.Images,
|
||||||
|
filename,
|
||||||
|
),
|
||||||
|
Buffer.from(data),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -85,12 +85,12 @@ export enum ObjectiveType {
|
|||||||
ArenaBotTags,
|
ArenaBotTags,
|
||||||
RecRoyaleGames = 3000,
|
RecRoyaleGames = 3000,
|
||||||
RecRoyaleWins,
|
RecRoyaleWins,
|
||||||
RecRoyaleTags
|
RecRoyaleTags,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Objective = {
|
export type Objective = {
|
||||||
type: ObjectiveType,
|
type: ObjectiveType;
|
||||||
score: number
|
score: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export * as Objectives from "./objectives.ts";
|
export * as Objectives from "./objectives.ts";
|
||||||
@@ -3,86 +3,171 @@ import Dictionary from "./usernames.ts";
|
|||||||
import { Config } from "../config.ts";
|
import { Config } from "../config.ts";
|
||||||
import { AuthType } from "./users.ts";
|
import { AuthType } from "./users.ts";
|
||||||
import * as JsonWebToken from "@gz/jwt";
|
import * as JsonWebToken from "@gz/jwt";
|
||||||
|
import { TokenBaseFormat } from "../apiutils.ts";
|
||||||
|
|
||||||
const config = Config.getConfig();
|
const config = Config.getConfig();
|
||||||
|
|
||||||
interface ProfileInitOptions {
|
interface ProfileInitOptions {
|
||||||
username: string
|
username: string;
|
||||||
}
|
}
|
||||||
interface AccountExport {
|
interface AccountExport {
|
||||||
accountId: number,
|
accountId: number;
|
||||||
profileImage: string,
|
profileImage: string;
|
||||||
isJunior: boolean,
|
isJunior: boolean;
|
||||||
platforms: number,
|
platforms: number;
|
||||||
username: string,
|
username: string;
|
||||||
displayName: string
|
displayName: string;
|
||||||
}
|
}
|
||||||
export type ProfileTokenFormat = {
|
export interface ProfileTokenFormat extends TokenBaseFormat {
|
||||||
iss: string;
|
|
||||||
sub: number;
|
sub: number;
|
||||||
nbf: number;
|
typ: AuthType.Game;
|
||||||
iat: number;
|
|
||||||
exp: number;
|
|
||||||
typ: AuthType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Profile {
|
class Profile {
|
||||||
|
static async exists(id: number) {
|
||||||
|
return (await Redis.Database.exists(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Profiles.Root,
|
||||||
|
id.toString(),
|
||||||
|
Redis.KeyGroups.Profiles.Username,
|
||||||
|
),
|
||||||
|
)) >= 1;
|
||||||
|
}
|
||||||
|
|
||||||
static async getUniqueId() {
|
static async getUniqueId() {
|
||||||
let id = Math.round(Math.random() * Math.pow(2, 31));
|
let id = Math.round(Math.random() * Math.pow(2, 31));
|
||||||
while ((await Redis.Database.exists(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Username))) >= 1)
|
while (
|
||||||
|
(await Redis.Database.exists(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Profiles.Root,
|
||||||
|
id.toString(),
|
||||||
|
Redis.KeyGroups.Profiles.Username,
|
||||||
|
),
|
||||||
|
)) >= 1
|
||||||
|
) {
|
||||||
id = await this.getUniqueId();
|
id = await this.getUniqueId();
|
||||||
|
}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async byName(name: string) {
|
static async byName(name: string) {
|
||||||
const id = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profile_Usernames, name));
|
const id = await Redis.Database.get(
|
||||||
|
Redis.buildKey(Redis.KeyGroups.Profile_Usernames, name),
|
||||||
|
);
|
||||||
if (id == null) return null;
|
if (id == null) return null;
|
||||||
else return new Profile(parseInt(id, 10));
|
else return new Profile(parseInt(id, 10));
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getUniqueUsername() {
|
static async getUniqueUsername() {
|
||||||
let username = `${Dictionary.Adjectives[Math.floor(Math.random() * Dictionary.Adjectives.length)]}${Dictionary.Nouns[Math.floor(Math.random() * Dictionary.Nouns.length)]}${Math.round(Math.random() * 10000)}`
|
let username = `${
|
||||||
while ((await Profile.byName(username)) !== null) username = await this.getUniqueUsername();
|
Dictionary
|
||||||
|
.Adjectives[
|
||||||
|
Math.floor(Math.random() * Dictionary.Adjectives.length)
|
||||||
|
]
|
||||||
|
}${
|
||||||
|
Dictionary
|
||||||
|
.Nouns[Math.floor(Math.random() * Dictionary.Nouns.length)]
|
||||||
|
}${Math.round(Math.random() * 10000)}`;
|
||||||
|
while ((await Profile.byName(username)) !== null) {
|
||||||
|
username = await this.getUniqueUsername();
|
||||||
|
}
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async init(options?: ProfileInitOptions) {
|
static async init(options?: ProfileInitOptions) {
|
||||||
|
const optionsSpecified = typeof options !== "undefined";
|
||||||
const optionsSpecified = typeof options !== 'undefined';
|
|
||||||
|
|
||||||
const newId = await this.getUniqueId();
|
const newId = await this.getUniqueId();
|
||||||
const newUsername = optionsSpecified ? options.username : await this.getUniqueUsername();
|
const newUsername = optionsSpecified
|
||||||
|
? options.username
|
||||||
|
: await this.getUniqueUsername();
|
||||||
|
|
||||||
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profile_Usernames, newUsername), newId);
|
await Redis.Database.set(
|
||||||
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profiles.Root, newId.toString(), Redis.KeyGroups.Profiles.Username), newUsername);
|
Redis.buildKey(Redis.KeyGroups.Profile_Usernames, newUsername),
|
||||||
|
newId,
|
||||||
|
);
|
||||||
|
await Redis.Database.set(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Profiles.Root,
|
||||||
|
newId.toString(),
|
||||||
|
Redis.KeyGroups.Profiles.Username,
|
||||||
|
),
|
||||||
|
newUsername,
|
||||||
|
);
|
||||||
|
|
||||||
return new Profile(newId);
|
return new Profile(newId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// surely this can be written better
|
// surely this can be written better
|
||||||
static getExportAccount(id: number): Promise<AccountExport | null> {
|
static getExportAccount(id: number): Promise<AccountExport | null> {
|
||||||
return new Promise((resolve, _reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Username)).then(val => {
|
Redis.Database.get(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Profiles.Root,
|
||||||
|
id.toString(),
|
||||||
|
Redis.KeyGroups.Profiles.Username,
|
||||||
|
),
|
||||||
|
).then((val) => {
|
||||||
if (val == null) resolve(null);
|
if (val == null) resolve(null);
|
||||||
else {
|
else {
|
||||||
const promises = {
|
const promises = {
|
||||||
profileImage: Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.ProfileImage)),
|
profileImage: Redis.Database.get(
|
||||||
isJunior: Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Junior)),
|
Redis.buildKey(
|
||||||
platforms: Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Platforms)),
|
Redis.KeyGroups.Profiles.Root,
|
||||||
displayName: Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.DisplayName)),
|
id.toString(),
|
||||||
username: Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Username)),
|
Redis.KeyGroups.Profiles.ProfileImage,
|
||||||
}
|
),
|
||||||
|
),
|
||||||
|
isJunior: Redis.Database.get(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Profiles.Root,
|
||||||
|
id.toString(),
|
||||||
|
Redis.KeyGroups.Profiles.Junior,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
platforms: Redis.Database.get(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Profiles.Root,
|
||||||
|
id.toString(),
|
||||||
|
Redis.KeyGroups.Profiles.Platforms,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
displayName: Redis.Database.get(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Profiles.Root,
|
||||||
|
id.toString(),
|
||||||
|
Redis.KeyGroups.Profiles.DisplayName,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
username: Redis.Database.get(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Profiles.Root,
|
||||||
|
id.toString(),
|
||||||
|
Redis.KeyGroups.Profiles.Username,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
Promise.all(Object.values(promises)).then((values) => {
|
Promise.all(Object.values(promises)).then((values) => {
|
||||||
resolve({
|
resolve({
|
||||||
accountId: id,
|
accountId: id,
|
||||||
profileImage: values[0] == null ? "DefaultProfileImage" : values[0],
|
profileImage: values[0] == null
|
||||||
isJunior: values[1] == null ? false : JSON.parse(values[1]),
|
? "DefaultProfileImage"
|
||||||
platforms: values[2] == null ? 1 : JSON.parse(values[2]),
|
: values[0],
|
||||||
displayName: values[3] == null ? (values[4] == null ? "DATABASEERROR" : values[4]) : values[3],
|
isJunior: values[1] == null
|
||||||
username: values[4] == null ? "DATABASEERROR" : values[4],
|
? false
|
||||||
|
: JSON.parse(values[1]),
|
||||||
|
platforms: values[2] == null
|
||||||
|
? 1
|
||||||
|
: JSON.parse(values[2]),
|
||||||
|
displayName: values[3] == null
|
||||||
|
? (values[4] == null
|
||||||
|
? "DATABASEERROR"
|
||||||
|
: values[4])
|
||||||
|
: values[3],
|
||||||
|
username: values[4] == null
|
||||||
|
? "DATABASEERROR"
|
||||||
|
: values[4],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -91,10 +176,10 @@ class Profile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async getExportAccountsBulk(ids: number[]) {
|
static async getExportAccountsBulk(ids: number[]) {
|
||||||
|
const accs = await Promise.all(
|
||||||
const accs = await Promise.all(ids.map(val => this.getExportAccount(val)));
|
ids.map((val) => this.getExportAccount(val)),
|
||||||
return accs.filter(val => val !== null);
|
);
|
||||||
|
return accs.filter((val) => val !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
#id: number;
|
#id: number;
|
||||||
@@ -117,12 +202,14 @@ class Profile {
|
|||||||
sub: this.#id,
|
sub: this.#id,
|
||||||
nbf: Math.round(Date.now() / 1000) - 200,
|
nbf: Math.round(Date.now() / 1000) - 200,
|
||||||
iat: Math.round(Date.now() / 1000),
|
iat: Math.round(Date.now() / 1000),
|
||||||
exp: Math.round(Date.now() / 1000) + (config.auth.timeout * 60 * 60),
|
exp: Math.round(Date.now() / 1000) +
|
||||||
typ: AuthType.Web
|
(config.auth.timeout * 60 * 60),
|
||||||
|
typ: AuthType.Game,
|
||||||
};
|
};
|
||||||
return await JsonWebToken.encode(payload, config.auth.secret, { algorithm: "HS512" });
|
return await JsonWebToken.encode(payload, config.auth.secret, {
|
||||||
|
algorithm: "HS512",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Profile;
|
export default Profile;
|
||||||
@@ -1,6 +1,301 @@
|
|||||||
const Dictionary = {
|
const Dictionary = {
|
||||||
Adjectives: ["Amazing","Adventurous","Affable","Agreeable","Ambitious","Amicable","Animated","Approachable","Articulate","Astute","Attractive","Authentic","Benevolent","Blissful","Bold","Bright","Buoyant","Calm","Captivating","Charismatic","Cheerful","Clever","Compassionate","Confident","Considerate","Content","Cooperative","Courageous","Creative","Cultured","Curious","Dashing","Dazzling","Dedicated","Delightful","Dependable","Determined","Diligent","Dynamic","Earnest","Easygoing","Ebullient","Effervescent","Empathetic","Enchanting","Endearing","Energetic","Engaging","Enthusiastic","Exuberant","Fantastic","Fearless","Fervent","Friendly","Funny","Generous","Gentle","Genuine","Gracious","Grateful","Harmonious","Heartwarming","Helpful","Honest","Humble","Humorous","Imaginative","Impeccable","Incisive","Incredible","Independent","Industrious","Ingenious","Insightful","Intelligent","Intuitive","Invigorating","Jovial","Jubilant","Just","Kind","Knowledgeable","Likable","Lively","Lovable","Loving","Loyal","Luminous","Magnetic","Marvelous","Masterful","Mature","Merciful","Methodical","Meticulous","Mindful","Motivated","Natural","Nurturing","Observant","Optimistic","Outgoing","Passionate","Patient","Peaceful","Perceptive","Perseverant","Persistent","Persuasive","Personable","Philanthropic","Placid","Playful","Pleasant","Poised","Positive","Powerful","Pragmatic","Proactive","Proficient","Prudent","Punctual","Purposeful","Radiant","Rational","Real","Receptive","Reflective","Reliable","Resilient","Resourceful","Respectful","Responsible","Robust","Sagacious","Serene","Sincere","Skillful","Smart","Sociable","Spirited","Splendid","Spontaneous","Steady","Sterling","Strong","Sublime","Successful","Supportive","Sympathetic","Talented","Tenacious","Thoughtful","Tireless","Tolerant","Tough","Tranquil","Trustworthy","Unassuming","Understanding","Unique","Unpretentious","Upbeat","Valiant","Vibrant","Virtuous","Visionary","Vivacious","Warmhearted","Welcoming","Wise","Witty","Wonderful","Zealous"],
|
Adjectives: [
|
||||||
Nouns: ["Nomad","Solstice","Elysium","Horizon","Catalyst","Luminescence","Utopia","Eclipse","Nebula","Arcadia","Apex","Harmony","Zenith","Radiant","Infinity","Echo","Quasar","Cascade","Empyrean","Nebula","Odyssey","Aether","Empower","Zephyr","Vibrance","Astral","Jubilant","Ascendancy","Zen","Nebulous","Ecliptic","Stellar","Quantum","Ethereal","Nexus","Synergy","Quantum","Enigma","Luminous","Epoch","Serendipity","Zenithal","Paragon","Panorama","Maverick","Voyager","Luminary","Catalyst","Phoenix","Dynamo","Zenith","Nexus","Pinnacle","Rhapsody","Serenity","Quantum","Apex","Harmony","Odyssey","Endeavor","Visionary","Epoch","Renaissance","Panache","Jubilee","Resonance","Zen","Nimbus","Ethereal","Cascade","Radiance","Synchronicity","Nebula","Equinox","Pulsar","Apex","Ethos","Wanderlust","Zenith","Nebula","Vertex","Equinox","Odyssey","Pantheon","Elysian","Nebulous","Quantum","Harmonic","Luminance","Paragon","Radiant","Epoch","Vortex","Celestia","Infinitum","Empyrean","Zephyr","Nimbus","Seraphic","Enigma","Synergy","Ecliptic","Utopian","Phoenix","Catalyst","Euphoria","Astral","Nebula","Ethereal","Zenith","Nexus","Empower","Panorama","Cascade","Quantum","Jubilant","Zen","Radiance","Labyrinth"]
|
"Amazing",
|
||||||
}
|
"Adventurous",
|
||||||
|
"Affable",
|
||||||
|
"Agreeable",
|
||||||
|
"Ambitious",
|
||||||
|
"Amicable",
|
||||||
|
"Animated",
|
||||||
|
"Approachable",
|
||||||
|
"Articulate",
|
||||||
|
"Astute",
|
||||||
|
"Attractive",
|
||||||
|
"Authentic",
|
||||||
|
"Benevolent",
|
||||||
|
"Blissful",
|
||||||
|
"Bold",
|
||||||
|
"Bright",
|
||||||
|
"Buoyant",
|
||||||
|
"Calm",
|
||||||
|
"Captivating",
|
||||||
|
"Charismatic",
|
||||||
|
"Cheerful",
|
||||||
|
"Clever",
|
||||||
|
"Compassionate",
|
||||||
|
"Confident",
|
||||||
|
"Considerate",
|
||||||
|
"Content",
|
||||||
|
"Cooperative",
|
||||||
|
"Courageous",
|
||||||
|
"Creative",
|
||||||
|
"Cultured",
|
||||||
|
"Curious",
|
||||||
|
"Dashing",
|
||||||
|
"Dazzling",
|
||||||
|
"Dedicated",
|
||||||
|
"Delightful",
|
||||||
|
"Dependable",
|
||||||
|
"Determined",
|
||||||
|
"Diligent",
|
||||||
|
"Dynamic",
|
||||||
|
"Earnest",
|
||||||
|
"Easygoing",
|
||||||
|
"Ebullient",
|
||||||
|
"Effervescent",
|
||||||
|
"Empathetic",
|
||||||
|
"Enchanting",
|
||||||
|
"Endearing",
|
||||||
|
"Energetic",
|
||||||
|
"Engaging",
|
||||||
|
"Enthusiastic",
|
||||||
|
"Exuberant",
|
||||||
|
"Fantastic",
|
||||||
|
"Fearless",
|
||||||
|
"Fervent",
|
||||||
|
"Friendly",
|
||||||
|
"Funny",
|
||||||
|
"Generous",
|
||||||
|
"Gentle",
|
||||||
|
"Genuine",
|
||||||
|
"Gracious",
|
||||||
|
"Grateful",
|
||||||
|
"Harmonious",
|
||||||
|
"Heartwarming",
|
||||||
|
"Helpful",
|
||||||
|
"Honest",
|
||||||
|
"Humble",
|
||||||
|
"Humorous",
|
||||||
|
"Imaginative",
|
||||||
|
"Impeccable",
|
||||||
|
"Incisive",
|
||||||
|
"Incredible",
|
||||||
|
"Independent",
|
||||||
|
"Industrious",
|
||||||
|
"Ingenious",
|
||||||
|
"Insightful",
|
||||||
|
"Intelligent",
|
||||||
|
"Intuitive",
|
||||||
|
"Invigorating",
|
||||||
|
"Jovial",
|
||||||
|
"Jubilant",
|
||||||
|
"Just",
|
||||||
|
"Kind",
|
||||||
|
"Knowledgeable",
|
||||||
|
"Likable",
|
||||||
|
"Lively",
|
||||||
|
"Lovable",
|
||||||
|
"Loving",
|
||||||
|
"Loyal",
|
||||||
|
"Luminous",
|
||||||
|
"Magnetic",
|
||||||
|
"Marvelous",
|
||||||
|
"Masterful",
|
||||||
|
"Mature",
|
||||||
|
"Merciful",
|
||||||
|
"Methodical",
|
||||||
|
"Meticulous",
|
||||||
|
"Mindful",
|
||||||
|
"Motivated",
|
||||||
|
"Natural",
|
||||||
|
"Nurturing",
|
||||||
|
"Observant",
|
||||||
|
"Optimistic",
|
||||||
|
"Outgoing",
|
||||||
|
"Passionate",
|
||||||
|
"Patient",
|
||||||
|
"Peaceful",
|
||||||
|
"Perceptive",
|
||||||
|
"Perseverant",
|
||||||
|
"Persistent",
|
||||||
|
"Persuasive",
|
||||||
|
"Personable",
|
||||||
|
"Philanthropic",
|
||||||
|
"Placid",
|
||||||
|
"Playful",
|
||||||
|
"Pleasant",
|
||||||
|
"Poised",
|
||||||
|
"Positive",
|
||||||
|
"Powerful",
|
||||||
|
"Pragmatic",
|
||||||
|
"Proactive",
|
||||||
|
"Proficient",
|
||||||
|
"Prudent",
|
||||||
|
"Punctual",
|
||||||
|
"Purposeful",
|
||||||
|
"Radiant",
|
||||||
|
"Rational",
|
||||||
|
"Real",
|
||||||
|
"Receptive",
|
||||||
|
"Reflective",
|
||||||
|
"Reliable",
|
||||||
|
"Resilient",
|
||||||
|
"Resourceful",
|
||||||
|
"Respectful",
|
||||||
|
"Responsible",
|
||||||
|
"Robust",
|
||||||
|
"Sagacious",
|
||||||
|
"Serene",
|
||||||
|
"Sincere",
|
||||||
|
"Skillful",
|
||||||
|
"Smart",
|
||||||
|
"Sociable",
|
||||||
|
"Spirited",
|
||||||
|
"Splendid",
|
||||||
|
"Spontaneous",
|
||||||
|
"Steady",
|
||||||
|
"Sterling",
|
||||||
|
"Strong",
|
||||||
|
"Sublime",
|
||||||
|
"Successful",
|
||||||
|
"Supportive",
|
||||||
|
"Sympathetic",
|
||||||
|
"Talented",
|
||||||
|
"Tenacious",
|
||||||
|
"Thoughtful",
|
||||||
|
"Tireless",
|
||||||
|
"Tolerant",
|
||||||
|
"Tough",
|
||||||
|
"Tranquil",
|
||||||
|
"Trustworthy",
|
||||||
|
"Unassuming",
|
||||||
|
"Understanding",
|
||||||
|
"Unique",
|
||||||
|
"Unpretentious",
|
||||||
|
"Upbeat",
|
||||||
|
"Valiant",
|
||||||
|
"Vibrant",
|
||||||
|
"Virtuous",
|
||||||
|
"Visionary",
|
||||||
|
"Vivacious",
|
||||||
|
"Warmhearted",
|
||||||
|
"Welcoming",
|
||||||
|
"Wise",
|
||||||
|
"Witty",
|
||||||
|
"Wonderful",
|
||||||
|
"Zealous",
|
||||||
|
],
|
||||||
|
Nouns: [
|
||||||
|
"Nomad",
|
||||||
|
"Solstice",
|
||||||
|
"Elysium",
|
||||||
|
"Horizon",
|
||||||
|
"Catalyst",
|
||||||
|
"Luminescence",
|
||||||
|
"Utopia",
|
||||||
|
"Eclipse",
|
||||||
|
"Nebula",
|
||||||
|
"Arcadia",
|
||||||
|
"Apex",
|
||||||
|
"Harmony",
|
||||||
|
"Zenith",
|
||||||
|
"Radiant",
|
||||||
|
"Infinity",
|
||||||
|
"Echo",
|
||||||
|
"Quasar",
|
||||||
|
"Cascade",
|
||||||
|
"Empyrean",
|
||||||
|
"Nebula",
|
||||||
|
"Odyssey",
|
||||||
|
"Aether",
|
||||||
|
"Empower",
|
||||||
|
"Zephyr",
|
||||||
|
"Vibrance",
|
||||||
|
"Astral",
|
||||||
|
"Jubilant",
|
||||||
|
"Ascendancy",
|
||||||
|
"Zen",
|
||||||
|
"Nebulous",
|
||||||
|
"Ecliptic",
|
||||||
|
"Stellar",
|
||||||
|
"Quantum",
|
||||||
|
"Ethereal",
|
||||||
|
"Nexus",
|
||||||
|
"Synergy",
|
||||||
|
"Quantum",
|
||||||
|
"Enigma",
|
||||||
|
"Luminous",
|
||||||
|
"Epoch",
|
||||||
|
"Serendipity",
|
||||||
|
"Zenithal",
|
||||||
|
"Paragon",
|
||||||
|
"Panorama",
|
||||||
|
"Maverick",
|
||||||
|
"Voyager",
|
||||||
|
"Luminary",
|
||||||
|
"Catalyst",
|
||||||
|
"Phoenix",
|
||||||
|
"Dynamo",
|
||||||
|
"Zenith",
|
||||||
|
"Nexus",
|
||||||
|
"Pinnacle",
|
||||||
|
"Rhapsody",
|
||||||
|
"Serenity",
|
||||||
|
"Quantum",
|
||||||
|
"Apex",
|
||||||
|
"Harmony",
|
||||||
|
"Odyssey",
|
||||||
|
"Endeavor",
|
||||||
|
"Visionary",
|
||||||
|
"Epoch",
|
||||||
|
"Renaissance",
|
||||||
|
"Panache",
|
||||||
|
"Jubilee",
|
||||||
|
"Resonance",
|
||||||
|
"Zen",
|
||||||
|
"Nimbus",
|
||||||
|
"Ethereal",
|
||||||
|
"Cascade",
|
||||||
|
"Radiance",
|
||||||
|
"Synchronicity",
|
||||||
|
"Nebula",
|
||||||
|
"Equinox",
|
||||||
|
"Pulsar",
|
||||||
|
"Apex",
|
||||||
|
"Ethos",
|
||||||
|
"Wanderlust",
|
||||||
|
"Zenith",
|
||||||
|
"Nebula",
|
||||||
|
"Vertex",
|
||||||
|
"Equinox",
|
||||||
|
"Odyssey",
|
||||||
|
"Pantheon",
|
||||||
|
"Elysian",
|
||||||
|
"Nebulous",
|
||||||
|
"Quantum",
|
||||||
|
"Harmonic",
|
||||||
|
"Luminance",
|
||||||
|
"Paragon",
|
||||||
|
"Radiant",
|
||||||
|
"Epoch",
|
||||||
|
"Vortex",
|
||||||
|
"Celestia",
|
||||||
|
"Infinitum",
|
||||||
|
"Empyrean",
|
||||||
|
"Zephyr",
|
||||||
|
"Nimbus",
|
||||||
|
"Seraphic",
|
||||||
|
"Enigma",
|
||||||
|
"Synergy",
|
||||||
|
"Ecliptic",
|
||||||
|
"Utopian",
|
||||||
|
"Phoenix",
|
||||||
|
"Catalyst",
|
||||||
|
"Euphoria",
|
||||||
|
"Astral",
|
||||||
|
"Nebula",
|
||||||
|
"Ethereal",
|
||||||
|
"Zenith",
|
||||||
|
"Nexus",
|
||||||
|
"Empower",
|
||||||
|
"Panorama",
|
||||||
|
"Cascade",
|
||||||
|
"Quantum",
|
||||||
|
"Jubilant",
|
||||||
|
"Zen",
|
||||||
|
"Radiance",
|
||||||
|
"Labyrinth",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export default Dictionary;
|
export default Dictionary;
|
||||||
@@ -2,36 +2,38 @@ import { Redis } from "../db.ts";
|
|||||||
import * as JsonWebToken from "@gz/jwt";
|
import * as JsonWebToken from "@gz/jwt";
|
||||||
import { Config } from "../config.ts";
|
import { Config } from "../config.ts";
|
||||||
import Profile from "./profiles.ts";
|
import Profile from "./profiles.ts";
|
||||||
|
import { TokenBaseFormat } from "../apiutils.ts";
|
||||||
|
|
||||||
type UserInitOptions = {
|
type UserInitOptions = {
|
||||||
client_id: string,
|
client_id: string;
|
||||||
pubkey: string
|
pubkey: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
type UserCreatedObj = {
|
type UserCreatedObj = {
|
||||||
user: User
|
user: User;
|
||||||
}
|
};
|
||||||
|
|
||||||
export enum AuthType {
|
export enum AuthType {
|
||||||
Game,
|
Game,
|
||||||
Web
|
Web,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UserTokenFormat = {
|
export interface UserTokenFormat extends TokenBaseFormat {
|
||||||
iss: string;
|
|
||||||
sub: string;
|
sub: string;
|
||||||
nbf: number;
|
typ: AuthType.Web;
|
||||||
iat: number;
|
|
||||||
exp: number;
|
|
||||||
typ: AuthType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = Config.getConfig();
|
const config = Config.getConfig();
|
||||||
|
|
||||||
export class User {
|
export class User {
|
||||||
|
|
||||||
static async exists(id: string) {
|
static async exists(id: string) {
|
||||||
return (await Redis.Database.exists(Redis.buildKey(Redis.KeyGroups.Users.Root, id, Redis.KeyGroups.Users.Pubkey))) == 1;
|
return (await Redis.Database.exists(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Users.Root,
|
||||||
|
id,
|
||||||
|
Redis.KeyGroups.Users.Pubkey,
|
||||||
|
),
|
||||||
|
)) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,7 +43,14 @@ export class User {
|
|||||||
static async init(options: UserInitOptions) {
|
static async init(options: UserInitOptions) {
|
||||||
if (await User.exists(options.client_id)) return null;
|
if (await User.exists(options.client_id)) return null;
|
||||||
|
|
||||||
Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Users.Root, options.client_id, Redis.KeyGroups.Users.Pubkey), options.pubkey);
|
Redis.Database.set(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Users.Root,
|
||||||
|
options.client_id,
|
||||||
|
Redis.KeyGroups.Users.Pubkey,
|
||||||
|
),
|
||||||
|
options.pubkey,
|
||||||
|
);
|
||||||
|
|
||||||
const user = new User(options.client_id);
|
const user = new User(options.client_id);
|
||||||
return user;
|
return user;
|
||||||
@@ -53,12 +62,18 @@ export class User {
|
|||||||
this.#client_id = client_id;
|
this.#client_id = client_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUuid() {
|
getId() {
|
||||||
return this.#client_id;
|
return this.#client_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async exists() {
|
async exists() {
|
||||||
return (await Redis.Database.exists(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Pubkey))) >= 1;
|
return (await Redis.Database.exists(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Users.Root,
|
||||||
|
this.#client_id,
|
||||||
|
Redis.KeyGroups.Users.Pubkey,
|
||||||
|
),
|
||||||
|
)) >= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getToken() {
|
async getToken() {
|
||||||
@@ -67,10 +82,13 @@ export class User {
|
|||||||
sub: this.#client_id,
|
sub: this.#client_id,
|
||||||
nbf: Math.round(Date.now() / 1000) - 200,
|
nbf: Math.round(Date.now() / 1000) - 200,
|
||||||
iat: Math.round(Date.now() / 1000),
|
iat: Math.round(Date.now() / 1000),
|
||||||
exp: Math.round(Date.now() / 1000) + (config.auth.timeout * 60 * 60),
|
exp: Math.round(Date.now() / 1000) +
|
||||||
typ: AuthType.Web
|
(config.auth.timeout * 60 * 60),
|
||||||
|
typ: AuthType.Web,
|
||||||
};
|
};
|
||||||
return await JsonWebToken.encode(payload, config.auth.secret, {algorithm: "HS512"});
|
return await JsonWebToken.encode(payload, config.auth.secret, {
|
||||||
|
algorithm: "HS512",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async exportAssociatedProfiles() {
|
async exportAssociatedProfiles() {
|
||||||
@@ -79,28 +97,83 @@ export class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getAssociatedProfiles() {
|
async getAssociatedProfiles() {
|
||||||
const list = await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Profiles));
|
const list = await Redis.Database.smembers(
|
||||||
return new Set<number>(list.filter(val => !Number.isNaN(parseInt(val, 10))).map(val => parseInt(val, 10)));
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Users.Root,
|
||||||
|
this.#client_id,
|
||||||
|
Redis.KeyGroups.Users.Profiles,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return new Set<number>(
|
||||||
|
list.filter((val) => !Number.isNaN(parseInt(val, 10))).map((val) =>
|
||||||
|
parseInt(val, 10)
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeAssociatedProfile(id: number) {
|
async removeAssociatedProfile(id: number) {
|
||||||
await Redis.Database.srem(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Profiles), id);
|
await Redis.Database.srem(
|
||||||
|
Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Users.Root,
|
||||||
|
this.#client_id,
|
||||||
|
Redis.KeyGroups.Users.Profiles,
|
||||||
|
),
|
||||||
|
id,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addAssociatedProfile(id: number) {
|
async addAssociatedProfile(id: number) {
|
||||||
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Profiles), id);
|
const dbkey = Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Users.Root,
|
||||||
|
this.#client_id,
|
||||||
|
Redis.KeyGroups.Users.Profiles,
|
||||||
|
);
|
||||||
|
if ((await Redis.Database.sismember(dbkey, id)) >= 1) return false;
|
||||||
|
await Redis.Database.sadd(dbkey, id);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addAssociatedPlatformId(id: string) {
|
async addAssociatedPlatformId(id: string) {
|
||||||
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.PlatformIds), id);
|
const dbkey = Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Users.Root,
|
||||||
|
this.#client_id,
|
||||||
|
Redis.KeyGroups.Users.AssociatedPlatforms,
|
||||||
|
);
|
||||||
|
if ((await Redis.Database.sismember(dbkey, id)) >= 1) return false;
|
||||||
|
await Redis.Database.sadd(dbkey, id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addAssociatedDeviceId(id: string) {
|
||||||
|
const dbkey = Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Users.Root,
|
||||||
|
this.#client_id,
|
||||||
|
Redis.KeyGroups.Users.AssociatedDeviceIds,
|
||||||
|
);
|
||||||
|
if ((await Redis.Database.sismember(dbkey, id)) >= 1) return false;
|
||||||
|
await Redis.Database.sadd(dbkey, id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async addAssociatedIp(ip: string) {
|
||||||
|
const dbkey = Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Users.Root,
|
||||||
|
this.#client_id,
|
||||||
|
Redis.KeyGroups.Users.AssociatedIps,
|
||||||
|
);
|
||||||
|
if ((await Redis.Database.sismember(dbkey, ip)) >= 1) return false;
|
||||||
|
await Redis.Database.sadd(dbkey, ip);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addNonce(str: string) {
|
async addNonce(str: string) {
|
||||||
await Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Nonces), str);
|
const dbkey = Redis.buildKey(
|
||||||
|
Redis.KeyGroups.Users.Root,
|
||||||
|
this.#client_id,
|
||||||
|
Redis.KeyGroups.Users.Nonces,
|
||||||
|
);
|
||||||
|
if ((await Redis.Database.sismember(dbkey, str)) >= 1) return false;
|
||||||
|
await Redis.Database.sadd(dbkey, str);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async hasNonce(str: string) {
|
|
||||||
return (await Redis.Database.sismember(Redis.buildKey(Redis.KeyGroups.Users.Root, this.#client_id, Redis.KeyGroups.Users.Nonces), str)) >= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
41
src/db.ts
@@ -6,16 +6,16 @@ import chalk from "npm:chalk@^5.3.0";
|
|||||||
const log = new Logging("Redis");
|
const log = new Logging("Redis");
|
||||||
|
|
||||||
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`);
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let shuttingDown = false;
|
let shuttingDown = false;
|
||||||
Deno.addSignalListener('SIGINT', () => {
|
Deno.addSignalListener("SIGINT", () => {
|
||||||
if (shuttingDown) return;
|
if (shuttingDown) return;
|
||||||
shuttingDown = true;
|
shuttingDown = true;
|
||||||
log.n('Disconnecting from Redis');
|
log.n("Disconnecting from Redis");
|
||||||
Database.quit();
|
Database.quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -25,30 +25,37 @@ export const Database = new Redis({
|
|||||||
username: config.redis.username == "" ? undefined : config.redis.username,
|
username: config.redis.username == "" ? undefined : config.redis.username,
|
||||||
password: config.redis.password == "" ? undefined : config.redis.password,
|
password: config.redis.password == "" ? undefined : config.redis.password,
|
||||||
db: config.redis.db,
|
db: config.redis.db,
|
||||||
lazyConnect: true
|
lazyConnect: true,
|
||||||
});
|
});
|
||||||
Database.on('connect', async () => {
|
Database.on("connect", async () => {
|
||||||
log.i(`Connected to Redis`);
|
log.i(`Connected to Redis`);
|
||||||
|
|
||||||
if (Deno.args.includes('--db-flush')) await Database.flushall(() => {
|
if (Deno.args.includes("--db-flush")) {
|
||||||
log.w(`${chalk.inverse('The database was flushed.')}`);
|
await Database.flushall(() => {
|
||||||
|
log.w(`${chalk.inverse("The database was flushed.")}`);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
Database.on('connecting', () => {
|
Database.on("connecting", () => {
|
||||||
log.n('Connecting to Redis..');
|
log.n("Connecting to Redis..");
|
||||||
});
|
});
|
||||||
Database.on('error', (err) => {
|
Database.on("error", (err) => {
|
||||||
log.e(`Redis error: ${err.stack}`);
|
log.e(`Redis error: ${err.stack}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
export function buildKey(...args: string[]) {
|
export function buildKey(...args: string[]) {
|
||||||
return args.join(':');
|
return args.join(":");
|
||||||
}
|
}
|
||||||
export const KeyGroups = {
|
export const KeyGroups = {
|
||||||
Config: {
|
Config: {
|
||||||
Root: "config",
|
Root: "config",
|
||||||
Dynamic: "dynamic",
|
Dynamic: "dynamic",
|
||||||
Game: "game"
|
Game: "game",
|
||||||
|
},
|
||||||
|
Content: {
|
||||||
|
Root: "content",
|
||||||
|
Images: "images",
|
||||||
|
Rooms: "rooms",
|
||||||
},
|
},
|
||||||
Profile_Usernames: "profile-usernames",
|
Profile_Usernames: "profile-usernames",
|
||||||
Profiles: {
|
Profiles: {
|
||||||
@@ -57,14 +64,16 @@ export const KeyGroups = {
|
|||||||
ProfileImage: "profileImage",
|
ProfileImage: "profileImage",
|
||||||
Junior: "isJunior",
|
Junior: "isJunior",
|
||||||
Platforms: "platforms",
|
Platforms: "platforms",
|
||||||
DisplayName: "displayname"
|
DisplayName: "displayname",
|
||||||
},
|
},
|
||||||
Users: {
|
Users: {
|
||||||
Root: "users",
|
Root: "users",
|
||||||
Profiles: "profiles",
|
Profiles: "profiles",
|
||||||
Pubkey: "pubkey",
|
Pubkey: "pubkey",
|
||||||
Nonces: "nonces",
|
Nonces: "nonces",
|
||||||
PlatformIds: "associatedPlatforms"
|
AssociatedPlatforms: "associatedPlatforms",
|
||||||
}
|
AssociatedDeviceIds: "associatedDeviceIds",
|
||||||
}
|
AssociatedIps: "associatedIps",
|
||||||
|
},
|
||||||
|
};
|
||||||
export * as Redis from "./db.ts";
|
export * as Redis from "./db.ts";
|
||||||
@@ -5,30 +5,37 @@ import Logging from "@proxnet/undead-logging";
|
|||||||
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`);
|
||||||
Deno.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,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
client.once(discord.Events.ClientReady, client => {
|
client.once(discord.Events.ClientReady, (client) => {
|
||||||
log.i(`Logged in to Discord as "${client.user.tag}"`);
|
log.i(`Logged in to Discord as "${client.user.tag}"`);
|
||||||
client.user?.setActivity(config.public.motd, { type: discord.ActivityType.Custom });
|
client.user?.setActivity(config.public.motd, {
|
||||||
|
type: discord.ActivityType.Custom,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let shuttingDown = false;
|
let shuttingDown = false;
|
||||||
Deno.addSignalListener('SIGINT', () => {
|
Deno.addSignalListener("SIGINT", () => {
|
||||||
if (client.readyTimestamp == null) return;
|
if (client.readyTimestamp == null) return;
|
||||||
if (shuttingDown) return;
|
if (shuttingDown) return;
|
||||||
shuttingDown = true;
|
shuttingDown = true;
|
||||||
log.n('Disconnecting from Discord');
|
log.n("Disconnecting from Discord");
|
||||||
client.destroy();
|
client.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
export function login() {
|
export function login() {
|
||||||
if (config.discord?.token == Config.defaultConfig.discord?.token) {
|
if (config.discord?.token == Config.defaultConfig.discord?.token) {
|
||||||
log.i('Discord not configured, ignoring');
|
log.i("Discord not configured, ignoring");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.i(`Creating Discord connection..`);
|
log.i(`Creating Discord connection..`);
|
||||||
|
|||||||
53
src/main.ts
@@ -15,16 +15,20 @@ log.i(`Starting Galvanic Corrosion..`);
|
|||||||
|
|
||||||
const config = Config.getConfig();
|
const config = Config.getConfig();
|
||||||
|
|
||||||
if (typeof config == 'undefined') {
|
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.auth.secret == Config.defaultConfig.auth.secret) {
|
if (config.auth.secret == Config.defaultConfig.auth.secret) {
|
||||||
log.e(`Cannot start: Auth secret is default. Please change 'secrets.authSecret' in 'config.json'`);
|
log.e(
|
||||||
|
`Cannot start: Auth secret is default. Please change 'secrets.authSecret' in 'config.json'`,
|
||||||
|
);
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
if (config.public.serverId == Config.defaultConfig.public.serverId) {
|
if (config.public.serverId == Config.defaultConfig.public.serverId) {
|
||||||
log.e(`Cannot start: Server ID is default. Please change 'public.serverId' in 'config.json'`);
|
log.e(
|
||||||
|
`Cannot start: Server ID is default. Please change 'public.serverId' in 'config.json'`,
|
||||||
|
);
|
||||||
Deno.exit(1);
|
Deno.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,41 +49,52 @@ log.n(`Starting HTTP server on http://${host}:${port}`);
|
|||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.disable('etag');
|
app.disable("etag");
|
||||||
app.disable('x-powered-by');
|
app.disable("x-powered-by");
|
||||||
|
|
||||||
app.use((rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
|
app.use(
|
||||||
rs.setHeader('Instance', instanceId)
|
(rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
|
||||||
|
rs.setHeader("Instance", instanceId);
|
||||||
log.n(`${APIUtils.getSrcIpDefault(rq)} ${rq.method} ${rq.originalUrl}`);
|
log.n(`${APIUtils.getSrcIpDefault(rq)} ${rq.method} ${rq.originalUrl}`);
|
||||||
nxt();
|
nxt();
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
app.get('/info', (_rq, rs) => {
|
app.get("/info", (_rq, rs) => {
|
||||||
rs.json({
|
rs.json({
|
||||||
name: config.public.serverName,
|
name: config.public.serverName,
|
||||||
id: config.public.serverId,
|
id: config.public.serverId,
|
||||||
motd: config.public.motd,
|
motd: config.public.motd,
|
||||||
patches: config.public.patches
|
patches: config.public.patches,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// content routes
|
// content routes
|
||||||
const nameserverRouter = await import('./routes/nameserver.ts');
|
const nameserverRouter = await import("./routes/nameserver.ts");
|
||||||
const apiRouter = await import('./routes/api.ts');
|
const apiRouter = await import("./routes/api.ts");
|
||||||
const userRouter = await import('./routes/user.ts');
|
const userRouter = await import("./routes/user.ts");
|
||||||
const authRouter = await import('./routes/auth.ts');
|
const authRouter = await import("./routes/auth.ts");
|
||||||
const accountRouter = await import('./routes/account.ts');
|
const accountRouter = await import("./routes/account.ts");
|
||||||
|
const imgRouter = await import("./routes/img.ts");
|
||||||
|
|
||||||
app.use(nameserverRouter.route.path, nameserverRouter.route.router);
|
app.use(nameserverRouter.route.path, nameserverRouter.route.router);
|
||||||
app.use(apiRouter.route.path, apiRouter.route.router);
|
app.use(apiRouter.route.path, apiRouter.route.router);
|
||||||
app.use(userRouter.route.path, userRouter.route.router);
|
app.use(userRouter.route.path, userRouter.route.router);
|
||||||
app.use(authRouter.route.path, authRouter.route.router);
|
app.use(authRouter.route.path, authRouter.route.router);
|
||||||
app.use(accountRouter.route.path, accountRouter.route.router);
|
app.use(accountRouter.route.path, accountRouter.route.router);
|
||||||
|
app.use(imgRouter.route.path, imgRouter.route.router);
|
||||||
|
|
||||||
app.use((rq: express.Request, rs: express.Response) => {
|
app.use((rq: express.Request, rs: express.Response) => {
|
||||||
log.e(`${APIUtils.getSrcIpDefault(rq)} 404 ${rq.method} ${rq.url.toString()}`);
|
log.e(
|
||||||
|
`${APIUtils.getSrcIpDefault(rq)} 404 ${rq.method} ${rq.url.toString()}`,
|
||||||
|
);
|
||||||
rs.statusCode = 404;
|
rs.statusCode = 404;
|
||||||
rs.json(APIUtils.genericResponseFormat(true, 'Endpoint not found. Check your syntax and/or method.'));
|
rs.json(
|
||||||
|
APIUtils.genericResponseFormat(
|
||||||
|
true,
|
||||||
|
"Endpoint not found. Check your syntax and/or method.",
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -87,7 +102,7 @@ try {
|
|||||||
log.n(`Listening on http://${config.web.host}:${config.web.port}`);
|
log.n(`Listening on http://${config.web.host}:${config.web.port}`);
|
||||||
|
|
||||||
let shuttingDown = false;
|
let shuttingDown = false;
|
||||||
Deno.addSignalListener('SIGINT', () => {
|
Deno.addSignalListener("SIGINT", () => {
|
||||||
if (shuttingDown) return;
|
if (shuttingDown) return;
|
||||||
shuttingDown = true;
|
shuttingDown = true;
|
||||||
log.i(`Shutting down`);
|
log.i(`Shutting down`);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { APIUtils } from "../apiutils.ts";
|
import { APIUtils } from "../apiutils.ts";
|
||||||
import { route as AccountRoute } from "./account/account.ts";
|
import { route as AccountRoute } from "./account/account.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter('/accountservice');
|
export const route = APIUtils.createRouter("/accountservice");
|
||||||
|
|
||||||
route.router.use(AccountRoute.path, AccountRoute.router);
|
route.router.use(AccountRoute.path, AccountRoute.router);
|
||||||
@@ -5,16 +5,23 @@ import Profile from "../../data/profiles.ts";
|
|||||||
export const route = APIUtils.createRouter("/account");
|
export const route = APIUtils.createRouter("/account");
|
||||||
|
|
||||||
interface CreateAccountRequestBody {
|
interface CreateAccountRequestBody {
|
||||||
platform: string,
|
platform: string;
|
||||||
platformId: string,
|
platformId: string;
|
||||||
deviceId: string
|
deviceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
route.router.post('/create',
|
const rateLimit = new APIUtils.RateLimiter(25, 5);
|
||||||
|
|
||||||
APIUtils.UserAuthentication,
|
route.router.post("/create",
|
||||||
|
|
||||||
|
rateLimit.middle(),
|
||||||
|
APIUtils.Authentication,
|
||||||
express.urlencoded({ extended: true }),
|
express.urlencoded({ extended: true }),
|
||||||
APIUtils.checkBodyTypes<CreateAccountRequestBody>({platform: "", platformId: "", deviceId: ""}),
|
APIUtils.checkBodyTypes<CreateAccountRequestBody>({
|
||||||
|
platform: "",
|
||||||
|
platformId: "",
|
||||||
|
deviceId: "",
|
||||||
|
}),
|
||||||
|
|
||||||
async (_rq, rs) => {
|
async (_rq, rs) => {
|
||||||
const newAcc = await Profile.init();
|
const newAcc = await Profile.init();
|
||||||
@@ -23,8 +30,43 @@ route.router.post('/create',
|
|||||||
|
|
||||||
rs.json({
|
rs.json({
|
||||||
success: true,
|
success: true,
|
||||||
value: await newAcc.export()
|
value: await newAcc.export(),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
route.router.get("/bulk",
|
||||||
|
|
||||||
|
rateLimit.middle(),
|
||||||
|
|
||||||
|
async (rq: express.Request, rs: express.Response) => {
|
||||||
|
|
||||||
|
if (typeof rq.query.id == "object") {
|
||||||
|
|
||||||
|
const ids = Object.values(rq.query.id).filter((val) => typeof val == "string").map((val) => parseInt(val, 10)).filter((val) => !isNaN(val));
|
||||||
|
rs.json([...await Profile.getExportAccountsBulk(ids)]);
|
||||||
|
|
||||||
|
} else if (typeof rq.query.id == "string") {
|
||||||
|
|
||||||
|
const id = parseInt(rq.query.id, 10);
|
||||||
|
if (isNaN(id)) {
|
||||||
|
rs.json(
|
||||||
|
APIUtils.genericResponseFormat(true, "Query data error"),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
rs.json(
|
||||||
|
[await Profile.getExportAccount(id)].filter((val) =>
|
||||||
|
val !== null
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
rs.json([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
);
|
);
|
||||||
@@ -3,7 +3,7 @@ import { route as ConfigRoute } from "./api/config.ts";
|
|||||||
import { route as GameConfig } from "./api/gameconfigs.ts";
|
import { route as GameConfig } from "./api/gameconfigs.ts";
|
||||||
import { APIUtils } from "../apiutils.ts";
|
import { APIUtils } from "../apiutils.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter('/api');
|
export const route = APIUtils.createRouter("/api");
|
||||||
|
|
||||||
route.router.use(VersionCheckRoute.path, VersionCheckRoute.router);
|
route.router.use(VersionCheckRoute.path, VersionCheckRoute.router);
|
||||||
route.router.use(ConfigRoute.path, ConfigRoute.router);
|
route.router.use(ConfigRoute.path, ConfigRoute.router);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { APIUtils } from "../../apiutils.ts";
|
import { APIUtils } from "../../apiutils.ts";
|
||||||
import { GameConfigs } from "../../data/config.ts";
|
import { GameConfigs } from "../../data/config.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter('/config');
|
export const route = APIUtils.createRouter("/config");
|
||||||
|
|
||||||
route.router.get('/v2', (_rq, rs) => {
|
route.router.get("/v2", (_rq, rs) => {
|
||||||
const config = GameConfigs.getConfig();
|
const config = GameConfigs.getConfig();
|
||||||
if (config == null) rs.sendStatus(500);
|
if (config == null) rs.sendStatus(500);
|
||||||
else rs.json(config);
|
else rs.json(config);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { APIUtils } from "../../apiutils.ts";
|
import { APIUtils } from "../../apiutils.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter('/gameconfigs');
|
export const route = APIUtils.createRouter("/gameconfigs");
|
||||||
|
|
||||||
route.router.get('/v1/all', (_rq, rs) => {
|
route.router.get("/v1/all", (_rq, rs) => {
|
||||||
rs.json([]);
|
rs.json([]);
|
||||||
});
|
});
|
||||||
@@ -1,34 +1,35 @@
|
|||||||
import { APIUtils } from "../../apiutils.ts";
|
import { APIUtils } from "../../apiutils.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter('/versioncheck');
|
export const route = APIUtils.createRouter("/versioncheck");
|
||||||
|
|
||||||
const validVersion = '20191120';
|
const validVersion = "20191120";
|
||||||
|
|
||||||
enum VersionStatus {
|
enum VersionStatus {
|
||||||
ValidForPlay,
|
ValidForPlay,
|
||||||
ValidForMenu,
|
ValidForMenu,
|
||||||
UpdateRequired
|
UpdateRequired,
|
||||||
}
|
}
|
||||||
type ValidVersionResponse = {
|
type ValidVersionResponse = {
|
||||||
VersionStatus: VersionStatus
|
VersionStatus: VersionStatus;
|
||||||
}
|
};
|
||||||
|
|
||||||
route.router.get('/v4', (rq, rs) => {
|
route.router.get("/v4", (rq, rs) => {
|
||||||
const requestedVer = rq.query['v'];
|
|
||||||
const pQuery = rq.query['p'];
|
const requestedVer = rq.query["v"];
|
||||||
if (typeof requestedVer == 'undefined' || typeof pQuery == 'undefined') {
|
const pQuery = rq.query["p"];
|
||||||
|
|
||||||
|
if (typeof requestedVer == "undefined" || typeof pQuery == "undefined") {
|
||||||
rs.statusCode = 400;
|
rs.statusCode = 400;
|
||||||
rs.json(APIUtils.genericResponseFormat(true, 'One or more query parameters were not found.'));
|
rs.json(APIUtils.genericResponseFormat(true, "One or more query parameters were not found."));
|
||||||
}
|
} else if (requestedVer !== validVersion) {
|
||||||
else if (requestedVer !== validVersion) {
|
|
||||||
const res: ValidVersionResponse = {
|
const res: ValidVersionResponse = {
|
||||||
VersionStatus: VersionStatus.UpdateRequired
|
VersionStatus: VersionStatus.UpdateRequired,
|
||||||
}
|
};
|
||||||
rs.json(res);
|
rs.json(res);
|
||||||
} else {
|
} else {
|
||||||
const res: ValidVersionResponse = {
|
const res: ValidVersionResponse = {
|
||||||
VersionStatus: VersionStatus.ValidForPlay
|
VersionStatus: VersionStatus.ValidForPlay,
|
||||||
}
|
};
|
||||||
rs.json(res);
|
rs.json(res);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2,7 +2,7 @@ import { APIUtils } from "../apiutils.ts";
|
|||||||
import { route as CachedLoginRoute } from "./auth/cachedlogin.ts";
|
import { route as CachedLoginRoute } from "./auth/cachedlogin.ts";
|
||||||
import { route as ConnectRoute } from "./auth/connect.ts";
|
import { route as ConnectRoute } from "./auth/connect.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter('/authservice');
|
export const route = APIUtils.createRouter("/authservice");
|
||||||
|
|
||||||
route.router.use(CachedLoginRoute.path, CachedLoginRoute.router);
|
route.router.use(CachedLoginRoute.path, CachedLoginRoute.router);
|
||||||
route.router.use(ConnectRoute.path, ConnectRoute.router);
|
route.router.use(ConnectRoute.path, ConnectRoute.router);
|
||||||
@@ -2,14 +2,19 @@ import { APIUtils } from "../../apiutils.ts";
|
|||||||
|
|
||||||
export const route = APIUtils.createRouter("/cachedlogin");
|
export const route = APIUtils.createRouter("/cachedlogin");
|
||||||
|
|
||||||
route.router.get('/forplatformid/:platformtype/:platformid',
|
route.router.get("/forplatformid/:platformtype/:platformid",
|
||||||
|
|
||||||
APIUtils.UserAuthentication,
|
APIUtils.Authentication,
|
||||||
|
|
||||||
async (_rq, rs) => {
|
async (_rq, rs) => {
|
||||||
|
const profiles = await rs.locals.user.exportAssociatedProfiles();
|
||||||
rs.json(await rs.locals.user.exportAssociatedProfiles());
|
rs.json(profiles.map((acc) => ({
|
||||||
|
platform: 0,
|
||||||
}
|
platformId: rs.locals.user.getId(),
|
||||||
|
accountId: acc.accountId,
|
||||||
|
lastLoginTime: new Date().toISOString(),
|
||||||
|
requirePassword: false,
|
||||||
|
})));
|
||||||
|
},
|
||||||
|
|
||||||
);
|
);
|
||||||
@@ -1,5 +1,94 @@
|
|||||||
import { APIUtils } from "../../apiutils.ts";
|
import { APIUtils, NoBody } from "../../apiutils.ts";
|
||||||
|
import express from "express";
|
||||||
|
import Profile from "../../data/profiles.ts";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter("/connect");
|
export const route = APIUtils.createRouter("/connect");
|
||||||
|
|
||||||
//route.router.post()
|
interface TokenRequestBody {
|
||||||
|
grant_type: string;
|
||||||
|
account_id: string;
|
||||||
|
client_id: string;
|
||||||
|
client_secret: string;
|
||||||
|
platform: string;
|
||||||
|
platform_id: string;
|
||||||
|
device_id: string;
|
||||||
|
device_class: string;
|
||||||
|
time: string;
|
||||||
|
ver: string;
|
||||||
|
asid: string;
|
||||||
|
platform_auth: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TokenResponseBody {
|
||||||
|
error?: string;
|
||||||
|
error_description?: string;
|
||||||
|
access_token: string;
|
||||||
|
refresh_token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
route.router.post("/token",
|
||||||
|
|
||||||
|
APIUtils.Authentication,
|
||||||
|
express.urlencoded({ extended: true }),
|
||||||
|
APIUtils.checkBodyTypes<TokenRequestBody>({
|
||||||
|
grant_type: "",
|
||||||
|
account_id: "",
|
||||||
|
client_id: "",
|
||||||
|
client_secret: "",
|
||||||
|
platform: "",
|
||||||
|
platform_id: "",
|
||||||
|
device_id: "",
|
||||||
|
device_class: "",
|
||||||
|
time: "",
|
||||||
|
ver: "",
|
||||||
|
asid: "",
|
||||||
|
platform_auth: "",
|
||||||
|
}),
|
||||||
|
|
||||||
|
async (
|
||||||
|
rq: express.Request<NoBody, NoBody, TokenRequestBody>,
|
||||||
|
rs: express.Response<TokenResponseBody>,
|
||||||
|
) => {
|
||||||
|
|
||||||
|
function requestFailed(msg: string = "invalid_request") {
|
||||||
|
rs.json({
|
||||||
|
error: msg,
|
||||||
|
access_token: "",
|
||||||
|
refresh_token: "",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conditionsMet = ![
|
||||||
|
rq.body.grant_type == "cached_login",
|
||||||
|
rq.body.client_id == "recroom",
|
||||||
|
rq.body.platform == "0",
|
||||||
|
rq.body.ver == '20191120',
|
||||||
|
!(rq.body.device_id.length > 96),
|
||||||
|
!(rq.body.client_secret.length > 96),
|
||||||
|
!(rq.body.platform_id.length > 32),
|
||||||
|
!(rq.body.time.length > 32),
|
||||||
|
!(rq.body.asid.length > 32),
|
||||||
|
].includes(false);
|
||||||
|
|
||||||
|
if (conditionsMet) {
|
||||||
|
const accounts = await rs.locals.user.getAssociatedProfiles();
|
||||||
|
const targetAccount = parseInt(rq.body.account_id);
|
||||||
|
|
||||||
|
if (isNaN(targetAccount)) requestFailed();
|
||||||
|
if (!accounts.has(targetAccount)) requestFailed("access_denied");
|
||||||
|
|
||||||
|
rs.locals.user.addAssociatedDeviceId(rq.body.device_id);
|
||||||
|
rs.locals.user.addAssociatedPlatformId(rq.body.platform_id);
|
||||||
|
|
||||||
|
const profile = new Profile(targetAccount);
|
||||||
|
if (!(await Profile.exists(profile.getId()))) requestFailed();
|
||||||
|
|
||||||
|
const token = await profile.getToken();
|
||||||
|
rs.json({
|
||||||
|
access_token: token,
|
||||||
|
refresh_token: token,
|
||||||
|
});
|
||||||
|
} else requestFailed();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
106
src/routes/img.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import { APIUtils, NoBody } from "../apiutils.ts";
|
||||||
|
import * as BaseImages from "../data/content/baseimages.ts";
|
||||||
|
import Logging from "@proxnet/undead-logging";
|
||||||
|
import express from "express";
|
||||||
|
import * as Images from "./../data/content/images.ts";
|
||||||
|
import { Image } from "https://deno.land/x/imagescript@1.3.0/mod.ts";
|
||||||
|
import { Buffer } from "node:buffer";
|
||||||
|
|
||||||
|
export const route = APIUtils.createRouter("/img");
|
||||||
|
|
||||||
|
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890. "
|
||||||
|
.split("");
|
||||||
|
function sanitizeString(input: string) {
|
||||||
|
return input.split("").filter((char) => chars.includes(char)).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseImages = BaseImages.getAllBaseImages();
|
||||||
|
|
||||||
|
interface ImageQueryOptions {
|
||||||
|
cropSquare?: string;
|
||||||
|
width?: string;
|
||||||
|
height?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
route.router.get(
|
||||||
|
"*",
|
||||||
|
async (
|
||||||
|
rq: express.Request<NoBody, NoBody, NoBody, ImageQueryOptions>,
|
||||||
|
rs: express.Response,
|
||||||
|
nxt: express.NextFunction,
|
||||||
|
) => {
|
||||||
|
const filename = sanitizeString(
|
||||||
|
rq.path.substring(1, rq.path.length).replaceAll("%20", " "),
|
||||||
|
);
|
||||||
|
|
||||||
|
// why does it think it is never reassigned? line 39
|
||||||
|
// deno-lint-ignore prefer-const
|
||||||
|
let image: Image;
|
||||||
|
const imageSource = baseImages.includes(filename)
|
||||||
|
? BaseImages.getBaseImage(filename)
|
||||||
|
: await Images.getImage(filename);
|
||||||
|
if (imageSource == null) {
|
||||||
|
nxt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
image = await Image.decode(imageSource);
|
||||||
|
|
||||||
|
let cropSquare: boolean = false;
|
||||||
|
if (typeof rq.query.cropSquare == "string") {
|
||||||
|
const d = JSON.parse(rq.query.cropSquare);
|
||||||
|
if (typeof d == "boolean" && d) cropSquare = true;
|
||||||
|
}
|
||||||
|
let width: number | null = null;
|
||||||
|
if (typeof rq.query.width == "string") {
|
||||||
|
const num = parseInt(rq.query.width);
|
||||||
|
if (isNaN(num)) width = null;
|
||||||
|
else width = num;
|
||||||
|
}
|
||||||
|
let height: number | null = null;
|
||||||
|
if (typeof rq.query.height == "string") {
|
||||||
|
const num = parseInt(rq.query.height);
|
||||||
|
if (isNaN(num)) height = null;
|
||||||
|
else height = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cropSquare) {
|
||||||
|
if (image.width > image.height) {
|
||||||
|
image.crop(
|
||||||
|
Math.round(image.width / 2) - Math.round(image.height / 2),
|
||||||
|
0,
|
||||||
|
image.height,
|
||||||
|
image.height,
|
||||||
|
);
|
||||||
|
} else {image.crop(
|
||||||
|
0,
|
||||||
|
Math.round(image.height / 2) - Math.round(image.width / 2),
|
||||||
|
image.width,
|
||||||
|
image.width,
|
||||||
|
);}
|
||||||
|
}
|
||||||
|
if (width && height) {
|
||||||
|
const targetWidth = width > image.width ? image.width : width;
|
||||||
|
const targetHeight = height > image.height ? image.height : height;
|
||||||
|
if (image.width > image.height) {
|
||||||
|
image.resize(Image.RESIZE_AUTO, height);
|
||||||
|
image.crop(
|
||||||
|
Math.round(image.width / 2) - Math.round(targetWidth / 2),
|
||||||
|
0,
|
||||||
|
targetWidth,
|
||||||
|
image.height,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
image.resize(width, Image.RESIZE_AUTO);
|
||||||
|
image.crop(
|
||||||
|
0,
|
||||||
|
Math.round(image.height / 2) - Math.round(targetHeight / 2),
|
||||||
|
image.width,
|
||||||
|
targetHeight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (width) image.resize(width, Image.RESIZE_AUTO);
|
||||||
|
else if (height) image.resize(Image.RESIZE_AUTO, height);
|
||||||
|
|
||||||
|
rs.type("png").send(Buffer.from(await image.encode()));
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -2,23 +2,23 @@ import { APIUtils } from "../apiutils.ts";
|
|||||||
import { Config } from "../config.ts";
|
import { Config } from "../config.ts";
|
||||||
|
|
||||||
const config = Config.getConfig() as Config.GalvanicConfiguration;
|
const config = Config.getConfig() as Config.GalvanicConfiguration;
|
||||||
const protocol = config.web.securepublichost ? 'https' : 'http';
|
const protocol = config.web.securepublichost ? "https" : "http";
|
||||||
|
|
||||||
export const route = APIUtils.createRouter('/ns');
|
export const route = APIUtils.createRouter("/ns");
|
||||||
|
|
||||||
type NameserverHosts = {
|
type NameserverHosts = {
|
||||||
Auth: string,
|
Auth: string;
|
||||||
API: string,
|
API: string;
|
||||||
WWW: string,
|
WWW: string;
|
||||||
Notifications: string,
|
Notifications: string;
|
||||||
Images: string,
|
Images: string;
|
||||||
CDN: string,
|
CDN: string;
|
||||||
Commerce: string,
|
Commerce: string;
|
||||||
Matchmaking: string,
|
Matchmaking: string;
|
||||||
Storage: string,
|
Storage: string;
|
||||||
Chat: string,
|
Chat: string;
|
||||||
Leaderboard: string
|
Leaderboard: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
const nameserver: NameserverHosts = {
|
const nameserver: NameserverHosts = {
|
||||||
Auth: `${protocol}://${config.web.publichost}/auth`,
|
Auth: `${protocol}://${config.web.publichost}/auth`,
|
||||||
@@ -31,9 +31,9 @@ const nameserver: NameserverHosts = {
|
|||||||
Matchmaking: `${protocol}://${config.web.publichost}/match`,
|
Matchmaking: `${protocol}://${config.web.publichost}/match`,
|
||||||
Storage: `${protocol}://${config.web.publichost}/storage`,
|
Storage: `${protocol}://${config.web.publichost}/storage`,
|
||||||
Chat: `${protocol}://${config.web.publichost}/chat`,
|
Chat: `${protocol}://${config.web.publichost}/chat`,
|
||||||
Leaderboard: `${protocol}://${config.web.publichost}/leaderboard`
|
Leaderboard: `${protocol}://${config.web.publichost}/leaderboard`,
|
||||||
}
|
};
|
||||||
|
|
||||||
route.router.get('*', (_rq, rs) => {
|
route.router.get("*", (_rq, rs) => {
|
||||||
rs.json(nameserver);
|
rs.json(nameserver);
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { APIUtils, NoBody } from "../apiutils.ts";
|
import { APIUtils, getSrcIpDefault, NoBody } from "../apiutils.ts";
|
||||||
// @ts-types = "npm:@types/express"
|
// @ts-types = "npm:@types/express"
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { User } from "../data/users.ts";
|
import { User } from "../data/users.ts";
|
||||||
@@ -10,25 +10,25 @@ const log = new Logging("UserRoute");
|
|||||||
|
|
||||||
const config = Config.getConfig();
|
const config = Config.getConfig();
|
||||||
|
|
||||||
export const route = APIUtils.createRouter('/user');
|
export const route = APIUtils.createRouter("/user");
|
||||||
|
|
||||||
interface AuthRequestSec {
|
interface AuthRequestSec {
|
||||||
timestamp: number,
|
timestamp: number;
|
||||||
nonce: string,
|
nonce: string;
|
||||||
server_id: string
|
server_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AuthRequestRoot {
|
interface AuthRequestRoot {
|
||||||
client_id: string,
|
client_id: string;
|
||||||
message: AuthRequestSec,
|
message: AuthRequestSec;
|
||||||
signature: string,
|
signature: string;
|
||||||
pubkey: string
|
pubkey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rateLimit = new APIUtils.RateLimiter(60, 1);
|
const rateLimit = new APIUtils.RateLimiter(60, 1);
|
||||||
|
|
||||||
route.router.post('/auth',
|
route.router.post(
|
||||||
|
"/auth",
|
||||||
rateLimit.middle(),
|
rateLimit.middle(),
|
||||||
express.json(),
|
express.json(),
|
||||||
APIUtils.checkBodyTypes<AuthRequestRoot>({
|
APIUtils.checkBodyTypes<AuthRequestRoot>({
|
||||||
@@ -36,72 +36,85 @@ route.router.post('/auth',
|
|||||||
message: {
|
message: {
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
nonce: "asdf",
|
nonce: "asdf",
|
||||||
server_id: "asdf"
|
server_id: "asdf",
|
||||||
},
|
},
|
||||||
signature: "asdf",
|
signature: "asdf",
|
||||||
pubkey: "asdf"
|
pubkey: "asdf",
|
||||||
}),
|
}),
|
||||||
|
async (
|
||||||
async (rq: express.Request<NoBody, NoBody, AuthRequestRoot>, rs: express.Response) => {
|
rq: express.Request<NoBody, NoBody, AuthRequestRoot>,
|
||||||
|
rs: express.Response,
|
||||||
|
) => {
|
||||||
function authFailed(msg: string) {
|
function authFailed(msg: string) {
|
||||||
rs.json(APIUtils.genericResponseFormat(true, msg));
|
rs.json(APIUtils.genericResponseFormat(true, msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rq.body.message.server_id !== config.public.serverId) {
|
if (rq.body.message.server_id !== config.public.serverId) {
|
||||||
log.w(`Auth request failed (serverId mismatch), config error?\n given ID: '${rq.body.message.server_id}'\n our ID: '${config.public.serverId}'`);
|
log.w(
|
||||||
authFailed('Authentication request not intended for this server.');
|
`Auth request failed (serverId mismatch), config error?\n given ID: '${rq.body.message.server_id}'\n our ID: '${config.public.serverId}'`,
|
||||||
|
);
|
||||||
|
authFailed("Authentication request not intended for this server.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const verify = crypto.createVerify('SHA256');
|
const verify = crypto.createVerify("SHA256");
|
||||||
verify.update(JSON.stringify(rq.body.message));
|
verify.update(JSON.stringify(rq.body.message));
|
||||||
verify.end();
|
verify.end();
|
||||||
|
|
||||||
const publicKey = await crypto.subtle.importKey(
|
const publicKey = await crypto.subtle.importKey(
|
||||||
"spki",
|
"spki",
|
||||||
(Uint8Array.from(atob(rq.body.pubkey), c => c.charCodeAt(0))).buffer,
|
(Uint8Array.from(atob(rq.body.pubkey), (c) => c.charCodeAt(0)))
|
||||||
|
.buffer,
|
||||||
{ name: "ECDSA", namedCurve: "P-256" },
|
{ name: "ECDSA", namedCurve: "P-256" },
|
||||||
false,
|
false,
|
||||||
["verify"]
|
["verify"],
|
||||||
|
);
|
||||||
|
const messageBytes = new TextEncoder().encode(
|
||||||
|
JSON.stringify(rq.body.message),
|
||||||
|
);
|
||||||
|
const signatureBytes = Uint8Array.from(
|
||||||
|
atob(rq.body.signature),
|
||||||
|
(c) => c.charCodeAt(0),
|
||||||
);
|
);
|
||||||
const messageBytes = new TextEncoder().encode(JSON.stringify(rq.body.message));
|
|
||||||
const signatureBytes = Uint8Array.from(atob(rq.body.signature), c => c.charCodeAt(0));
|
|
||||||
const isValid = await crypto.subtle.verify(
|
const isValid = await crypto.subtle.verify(
|
||||||
{ name: "ECDSA", hash: "SHA-256" },
|
{ name: "ECDSA", hash: "SHA-256" },
|
||||||
publicKey,
|
publicKey,
|
||||||
signatureBytes.buffer,
|
signatureBytes.buffer,
|
||||||
messageBytes
|
messageBytes,
|
||||||
);
|
);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
log.w(`Auth failed for clientId '${rq.body.client_id}'`);
|
log.w(`Auth failed for clientId '${rq.body.client_id}'`);
|
||||||
authFailed('Authentication request failed.');
|
authFailed("Authentication request failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.d(`Error when verifying auth request: ${err}`);
|
log.d(`Error when verifying auth request: ${err}`);
|
||||||
authFailed('Authentication request failed.');
|
authFailed("Authentication request failed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = new User(rq.body.client_id);
|
let user = new User(rq.body.client_id);
|
||||||
if (!(await user.exists())) {
|
if (!(await user.exists())) {
|
||||||
const obj = await User.init({ client_id: rq.body.client_id, pubkey: rq.body.pubkey });
|
const obj = await User.init({
|
||||||
|
client_id: rq.body.client_id,
|
||||||
|
pubkey: rq.body.pubkey,
|
||||||
|
});
|
||||||
if (obj == null) {
|
if (obj == null) {
|
||||||
rs.sendStatus(500);
|
rs.sendStatus(500);
|
||||||
return;
|
return;
|
||||||
} else user = obj;
|
} else user = obj;
|
||||||
}
|
}
|
||||||
if (await user.hasNonce(rq.body.message.nonce)) {
|
if (!(await user.addNonce(rq.body.message.nonce))) {
|
||||||
log.w(`Client '${rq.body.client_id}' has already used nonce. Replay attack?`);
|
log.w(
|
||||||
authFailed('Authentication request failed.');
|
`Client '${rq.body.client_id}' has already used nonce. Replay attack?`,
|
||||||
|
);
|
||||||
|
authFailed("Authentication request failed.");
|
||||||
return;
|
return;
|
||||||
} else user.addNonce(rq.body.message.nonce);
|
}
|
||||||
|
user.addAssociatedIp(getSrcIpDefault(rq));
|
||||||
|
|
||||||
const token = await user.getToken();
|
const token = await user.getToken();
|
||||||
rs.json({ token: token });
|
rs.json({ token: token });
|
||||||
|
},
|
||||||
}
|
|
||||||
|
|
||||||
);
|
);
|
||||||
@@ -4,8 +4,8 @@ import { User } from "../data/users.ts";
|
|||||||
declare global {
|
declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
interface Locals {
|
interface Locals {
|
||||||
profile: Profile
|
profile: Profile;
|
||||||
user: User
|
user: User;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||