This commit is contained in:
2025-09-03 14:32:02 -04:00
parent c6e2b6e4d3
commit 03b751dda6
22 changed files with 1429 additions and 150 deletions

7
.madgerc Normal file
View File

@@ -0,0 +1,7 @@
{
"detectiveOptions": {
"ts": {
"skipTypeImports": true
}
}
}

File diff suppressed because one or more lines are too long

View File

@@ -1,21 +0,0 @@
{
"CustomizationAllowed": false,
"EmptyOnSandboxClone": false,
"GameTeamColorSettings": {
"TeamOutfitColorEmissionEnabled": false,
"TeamOutfitColorEmissionAmount": 0.0,
"CustomTeamColors": []
},
"GiftContext": 1002,
"LevelRoomSubSceneNames": [],
"LocationEnum": 24,
"MaxPlayers": 1,
"Name": "splootyneam",
"ReleaseStatus": 2,
"ReplicationId": "aafb38f1-beb9-4af3-94e3-2985d502302a",
"RequiredSubSceneNames": [],
"SceneName": "SampleScene",
"SupportedGameMode": 0,
"SupportsMobile": true,
"SupportsVRLow": true
}

View File

@@ -1,21 +0,0 @@
{
"CustomizationAllowed": false,
"EmptyOnSandboxClone": false,
"GameTeamColorSettings": {
"TeamOutfitColorEmissionEnabled": false,
"TeamOutfitColorEmissionAmount": 0.0,
"CustomTeamColors": []
},
"GiftContext": 1002,
"LevelRoomSubSceneNames": [],
"LocationEnum": 24,
"MaxPlayers": 1,
"Name": "splootyneam",
"ReleaseStatus": 2,
"ReplicationId": "aafb38f1-beb9-4af3-94e3-2985d502302a",
"RequiredSubSceneNames": [],
"SceneName": "SampleScene",
"SupportedGameMode": 0,
"SupportsMobile": true,
"SupportsVRLow": true
}

866
deno.lock generated
View File

@@ -17,6 +17,7 @@
"jsr:@std/path@^1.1.1": "1.1.1",
"npm:@types/node@*": "22.15.15",
"npm:chalk@^5.3.0": "5.4.1",
"npm:madge@*": "8.0.0",
"npm:sharp@~0.34.3": "0.34.3",
"npm:zod@^4.0.5": "4.0.5"
},
@@ -82,6 +83,33 @@
}
},
"npm": {
"@babel/helper-string-parser@7.27.1": {
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="
},
"@babel/helper-validator-identifier@7.27.1": {
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="
},
"@babel/parser@7.28.0": {
"integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
"dependencies": [
"@babel/types"
],
"bin": true
},
"@babel/types@7.28.0": {
"integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==",
"dependencies": [
"@babel/helper-string-parser",
"@babel/helper-validator-identifier"
]
},
"@dependents/detective-less@5.0.1": {
"integrity": "sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==",
"dependencies": [
"gonzales-pe",
"node-source-walk"
]
},
"@emnapi/runtime@1.4.5": {
"integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==",
"dependencies": [
@@ -227,15 +255,199 @@
"os": ["win32"],
"cpu": ["x64"]
},
"@jridgewell/sourcemap-codec@1.5.4": {
"integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="
},
"@nodelib/fs.scandir@2.1.5": {
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dependencies": [
"@nodelib/fs.stat",
"run-parallel"
]
},
"@nodelib/fs.stat@2.0.5": {
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
},
"@nodelib/fs.walk@1.2.8": {
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dependencies": [
"@nodelib/fs.scandir",
"fastq"
]
},
"@ts-graphviz/adapter@2.0.6": {
"integrity": "sha512-kJ10lIMSWMJkLkkCG5gt927SnGZcBuG0s0HHswGzcHTgvtUe7yk5/3zTEr0bafzsodsOq5Gi6FhQeV775nC35Q==",
"dependencies": [
"@ts-graphviz/common"
]
},
"@ts-graphviz/ast@2.0.7": {
"integrity": "sha512-e6+2qtNV99UT6DJSoLbHfkzfyqY84aIuoV8Xlb9+hZAjgpum8iVHprGeAMQ4rF6sKUAxrmY8rfF/vgAwoPc3gw==",
"dependencies": [
"@ts-graphviz/common"
]
},
"@ts-graphviz/common@2.1.5": {
"integrity": "sha512-S6/9+T6x8j6cr/gNhp+U2olwo1n0jKj/682QVqsh7yXWV6ednHYqxFw0ZsY3LyzT0N8jaZ6jQY9YD99le3cmvg=="
},
"@ts-graphviz/core@2.0.7": {
"integrity": "sha512-w071DSzP94YfN6XiWhOxnLpYT3uqtxJBDYdh6Jdjzt+Ce6DNspJsPQgpC7rbts/B8tEkq0LHoYuIF/O5Jh5rPg==",
"dependencies": [
"@ts-graphviz/ast",
"@ts-graphviz/common"
]
},
"@types/node@22.15.15": {
"integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==",
"dependencies": [
"undici-types"
]
},
"@typescript-eslint/types@8.32.1": {
"integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg=="
},
"@typescript-eslint/typescript-estree@8.32.1_typescript@5.8.3": {
"integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==",
"dependencies": [
"@typescript-eslint/types",
"@typescript-eslint/visitor-keys",
"debug",
"fast-glob",
"is-glob",
"minimatch@9.0.5",
"semver",
"ts-api-utils",
"typescript"
]
},
"@typescript-eslint/visitor-keys@8.32.1": {
"integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==",
"dependencies": [
"@typescript-eslint/types",
"eslint-visitor-keys"
]
},
"@vue/compiler-core@3.5.17": {
"integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==",
"dependencies": [
"@babel/parser",
"@vue/shared",
"entities",
"estree-walker",
"source-map-js"
]
},
"@vue/compiler-dom@3.5.17": {
"integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==",
"dependencies": [
"@vue/compiler-core",
"@vue/shared"
]
},
"@vue/compiler-sfc@3.5.17": {
"integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==",
"dependencies": [
"@babel/parser",
"@vue/compiler-core",
"@vue/compiler-dom",
"@vue/compiler-ssr",
"@vue/shared",
"estree-walker",
"magic-string",
"postcss",
"source-map-js"
]
},
"@vue/compiler-ssr@3.5.17": {
"integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==",
"dependencies": [
"@vue/compiler-dom",
"@vue/shared"
]
},
"@vue/shared@3.5.17": {
"integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg=="
},
"ansi-regex@5.0.1": {
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles@4.3.0": {
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": [
"color-convert"
]
},
"any-promise@1.3.0": {
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="
},
"app-module-path@2.2.0": {
"integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ=="
},
"ast-module-types@6.0.1": {
"integrity": "sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA=="
},
"balanced-match@1.0.2": {
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base64-js@1.5.1": {
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"bl@4.1.0": {
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"dependencies": [
"buffer",
"inherits",
"readable-stream"
]
},
"brace-expansion@1.1.12": {
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dependencies": [
"balanced-match",
"concat-map"
]
},
"brace-expansion@2.0.2": {
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dependencies": [
"balanced-match"
]
},
"braces@3.0.3": {
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": [
"fill-range"
]
},
"buffer@5.7.1": {
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"dependencies": [
"base64-js",
"ieee754"
]
},
"chalk@4.1.2": {
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": [
"ansi-styles",
"supports-color"
]
},
"chalk@5.4.1": {
"integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="
},
"cli-cursor@3.1.0": {
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"dependencies": [
"restore-cursor"
]
},
"cli-spinners@2.9.2": {
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="
},
"clone@1.0.4": {
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="
},
"color-convert@2.0.1": {
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": [
@@ -259,12 +471,558 @@
"color-string"
]
},
"commander@12.1.0": {
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="
},
"commander@7.2.0": {
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="
},
"commondir@1.0.1": {
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
},
"concat-map@0.0.1": {
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"debug@4.4.1": {
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dependencies": [
"ms"
]
},
"deep-extend@0.6.0": {
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
},
"defaults@1.0.4": {
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
"dependencies": [
"clone"
]
},
"dependency-tree@11.1.1": {
"integrity": "sha512-pnkCd8VGOq70EVaEQxDC9mZCjCwYj4yG4j8h+PEJswuWp+rdE6p8zbtVvWk+yPwaVimOjlhNi782U9K5KOU9MQ==",
"dependencies": [
"commander@12.1.0",
"filing-cabinet",
"precinct",
"typescript"
],
"bin": true
},
"detect-libc@2.0.4": {
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="
},
"detective-amd@6.0.1": {
"integrity": "sha512-TtyZ3OhwUoEEIhTFoc1C9IyJIud3y+xYkSRjmvCt65+ycQuc3VcBrPRTMWoO/AnuCyOB8T5gky+xf7Igxtjd3g==",
"dependencies": [
"ast-module-types",
"escodegen",
"get-amd-module-type",
"node-source-walk"
],
"bin": true
},
"detective-cjs@6.0.1": {
"integrity": "sha512-tLTQsWvd2WMcmn/60T2inEJNhJoi7a//PQ7DwRKEj1yEeiQs4mrONgsUtEJKnZmrGWBBmE0kJ1vqOG/NAxwaJw==",
"dependencies": [
"ast-module-types",
"node-source-walk"
]
},
"detective-es6@5.0.1": {
"integrity": "sha512-XusTPuewnSUdoxRSx8OOI6xIA/uld/wMQwYsouvFN2LAg7HgP06NF1lHRV3x6BZxyL2Kkoih4ewcq8hcbGtwew==",
"dependencies": [
"node-source-walk"
]
},
"detective-postcss@7.0.1_postcss@8.5.6": {
"integrity": "sha512-bEOVpHU9picRZux5XnwGsmCN4+8oZo7vSW0O0/Enq/TO5R2pIAP2279NsszpJR7ocnQt4WXU0+nnh/0JuK4KHQ==",
"dependencies": [
"is-url",
"postcss",
"postcss-values-parser"
]
},
"detective-sass@6.0.1": {
"integrity": "sha512-jSGPO8QDy7K7pztUmGC6aiHkexBQT4GIH+mBAL9ZyBmnUIOFbkfZnO8wPRRJFP/QP83irObgsZHCoDHZ173tRw==",
"dependencies": [
"gonzales-pe",
"node-source-walk"
]
},
"detective-scss@5.0.1": {
"integrity": "sha512-MAyPYRgS6DCiS6n6AoSBJXLGVOydsr9huwXORUlJ37K3YLyiN0vYHpzs3AdJOgHobBfispokoqrEon9rbmKacg==",
"dependencies": [
"gonzales-pe",
"node-source-walk"
]
},
"detective-stylus@5.0.1": {
"integrity": "sha512-Dgn0bUqdGbE3oZJ+WCKf8Dmu7VWLcmRJGc6RCzBgG31DLIyai9WAoEhYRgIHpt/BCRMrnXLbGWGPQuBUrnF0TA=="
},
"detective-typescript@14.0.0_typescript@5.8.3": {
"integrity": "sha512-pgN43/80MmWVSEi5LUuiVvO/0a9ss5V7fwVfrJ4QzAQRd3cwqU1SfWGXJFcNKUqoD5cS+uIovhw5t/0rSeC5Mw==",
"dependencies": [
"@typescript-eslint/typescript-estree",
"ast-module-types",
"node-source-walk",
"typescript"
]
},
"detective-vue2@2.2.0_typescript@5.8.3": {
"integrity": "sha512-sVg/t6O2z1zna8a/UIV6xL5KUa2cMTQbdTIIvqNM0NIPswp52fe43Nwmbahzj3ww4D844u/vC2PYfiGLvD3zFA==",
"dependencies": [
"@dependents/detective-less",
"@vue/compiler-sfc",
"detective-es6",
"detective-sass",
"detective-scss",
"detective-stylus",
"detective-typescript",
"typescript"
]
},
"enhanced-resolve@5.18.1": {
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
"dependencies": [
"graceful-fs",
"tapable"
]
},
"entities@4.5.0": {
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
},
"escodegen@2.1.0": {
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"dependencies": [
"esprima",
"estraverse",
"esutils"
],
"optionalDependencies": [
"source-map"
],
"bin": true
},
"eslint-visitor-keys@4.2.0": {
"integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="
},
"esprima@4.0.1": {
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"bin": true
},
"estraverse@5.3.0": {
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
},
"estree-walker@2.0.2": {
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"esutils@2.0.3": {
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"fast-glob@3.3.3": {
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dependencies": [
"@nodelib/fs.stat",
"@nodelib/fs.walk",
"glob-parent",
"merge2",
"micromatch"
]
},
"fastq@1.19.1": {
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dependencies": [
"reusify"
]
},
"filing-cabinet@5.0.3": {
"integrity": "sha512-PlPcMwVWg60NQkhvfoxZs4wEHjhlOO/y7OAm4sKM60o1Z9nttRY4mcdQxp/iZ+kg/Vv6Hw1OAaTbYVM9DA9pYg==",
"dependencies": [
"app-module-path",
"commander@12.1.0",
"enhanced-resolve",
"module-definition",
"module-lookup-amd",
"resolve",
"resolve-dependency-path",
"sass-lookup",
"stylus-lookup",
"tsconfig-paths",
"typescript"
],
"bin": true
},
"fill-range@7.1.1": {
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dependencies": [
"to-regex-range"
]
},
"fs.realpath@1.0.0": {
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"function-bind@1.1.2": {
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
},
"get-amd-module-type@6.0.1": {
"integrity": "sha512-MtjsmYiCXcYDDrGqtNbeIYdAl85n+5mSv2r3FbzER/YV3ZILw4HNNIw34HuV5pyl0jzs6GFYU1VHVEefhgcNHQ==",
"dependencies": [
"ast-module-types",
"node-source-walk"
]
},
"get-own-enumerable-property-symbols@3.0.2": {
"integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g=="
},
"glob-parent@5.1.2": {
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dependencies": [
"is-glob"
]
},
"glob@7.2.3": {
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dependencies": [
"fs.realpath",
"inflight",
"inherits",
"minimatch@3.1.2",
"once",
"path-is-absolute"
],
"deprecated": true
},
"gonzales-pe@4.3.0": {
"integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==",
"dependencies": [
"minimist"
],
"bin": true
},
"graceful-fs@4.2.11": {
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"has-flag@4.0.0": {
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
},
"hasown@2.0.2": {
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dependencies": [
"function-bind"
]
},
"ieee754@1.2.1": {
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"inflight@1.0.6": {
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dependencies": [
"once",
"wrappy"
],
"deprecated": true
},
"inherits@2.0.4": {
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini@1.3.8": {
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
},
"is-arrayish@0.3.2": {
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
},
"is-core-module@2.16.1": {
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"dependencies": [
"hasown"
]
},
"is-extglob@2.1.1": {
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
},
"is-glob@4.0.3": {
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dependencies": [
"is-extglob"
]
},
"is-interactive@1.0.0": {
"integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="
},
"is-number@7.0.0": {
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-obj@1.0.1": {
"integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="
},
"is-regexp@1.0.0": {
"integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA=="
},
"is-unicode-supported@0.1.0": {
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="
},
"is-url-superb@4.0.0": {
"integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA=="
},
"is-url@1.2.4": {
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
},
"json5@2.2.3": {
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"bin": true
},
"log-symbols@4.1.0": {
"integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
"dependencies": [
"chalk@4.1.2",
"is-unicode-supported"
]
},
"madge@8.0.0": {
"integrity": "sha512-9sSsi3TBPhmkTCIpVQF0SPiChj1L7Rq9kU2KDG1o6v2XH9cCw086MopjVCD+vuoL5v8S77DTbVopTO8OUiQpIw==",
"dependencies": [
"chalk@4.1.2",
"commander@7.2.0",
"commondir",
"debug",
"dependency-tree",
"ora",
"pluralize",
"pretty-ms",
"rc",
"stream-to-array",
"ts-graphviz",
"walkdir"
],
"bin": true
},
"magic-string@0.30.17": {
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
"dependencies": [
"@jridgewell/sourcemap-codec"
]
},
"merge2@1.4.1": {
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
},
"micromatch@4.0.8": {
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dependencies": [
"braces",
"picomatch"
]
},
"mimic-fn@2.1.0": {
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
},
"minimatch@3.1.2": {
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": [
"brace-expansion@1.1.12"
]
},
"minimatch@9.0.5": {
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dependencies": [
"brace-expansion@2.0.2"
]
},
"minimist@1.2.8": {
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="
},
"module-definition@6.0.1": {
"integrity": "sha512-FeVc50FTfVVQnolk/WQT8MX+2WVcDnTGiq6Wo+/+lJ2ET1bRVi3HG3YlJUfqagNMc/kUlFSoR96AJkxGpKz13g==",
"dependencies": [
"ast-module-types",
"node-source-walk"
],
"bin": true
},
"module-lookup-amd@9.0.4": {
"integrity": "sha512-DWJEuLVvjxh5b8wrvJC5wr2a7qo7pOWXIgdCBNazU416kcIyzO4drxvlqKhsHzYwxcC4cWuhoK+MiWCKCGnv7A==",
"dependencies": [
"commander@12.1.0",
"glob",
"requirejs",
"requirejs-config-file"
],
"bin": true
},
"ms@2.1.3": {
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"nanoid@3.3.11": {
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"bin": true
},
"node-source-walk@7.0.1": {
"integrity": "sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg==",
"dependencies": [
"@babel/parser"
]
},
"once@1.4.0": {
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": [
"wrappy"
]
},
"onetime@5.1.2": {
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dependencies": [
"mimic-fn"
]
},
"ora@5.4.1": {
"integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
"dependencies": [
"bl",
"chalk@4.1.2",
"cli-cursor",
"cli-spinners",
"is-interactive",
"is-unicode-supported",
"log-symbols",
"strip-ansi",
"wcwidth"
]
},
"parse-ms@2.1.0": {
"integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA=="
},
"path-is-absolute@1.0.1": {
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
},
"path-parse@1.0.7": {
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"picocolors@1.1.1": {
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"picomatch@2.3.1": {
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
"pluralize@8.0.0": {
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="
},
"postcss-values-parser@6.0.2_postcss@8.5.6": {
"integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==",
"dependencies": [
"color-name",
"is-url-superb",
"postcss",
"quote-unquote"
]
},
"postcss@8.5.6": {
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dependencies": [
"nanoid",
"picocolors",
"source-map-js"
]
},
"precinct@12.2.0_postcss@8.5.6_typescript@5.8.3": {
"integrity": "sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w==",
"dependencies": [
"@dependents/detective-less",
"commander@12.1.0",
"detective-amd",
"detective-cjs",
"detective-es6",
"detective-postcss",
"detective-sass",
"detective-scss",
"detective-stylus",
"detective-typescript",
"detective-vue2",
"module-definition",
"node-source-walk",
"postcss",
"typescript"
],
"bin": true
},
"pretty-ms@7.0.1": {
"integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==",
"dependencies": [
"parse-ms"
]
},
"queue-microtask@1.2.3": {
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
},
"quote-unquote@1.0.0": {
"integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg=="
},
"rc@1.2.8": {
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dependencies": [
"deep-extend",
"ini",
"minimist",
"strip-json-comments"
],
"bin": true
},
"readable-stream@3.6.2": {
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dependencies": [
"inherits",
"string_decoder",
"util-deprecate"
]
},
"requirejs-config-file@4.0.0": {
"integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==",
"dependencies": [
"esprima",
"stringify-object"
]
},
"requirejs@2.3.7": {
"integrity": "sha512-DouTG8T1WanGok6Qjg2SXuCMzszOo0eHeH9hDZ5Y4x8Je+9JB38HdTLT4/VA8OaUhBa0JPVHJ0pyBkM1z+pDsw==",
"bin": true
},
"resolve-dependency-path@4.0.1": {
"integrity": "sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ=="
},
"resolve@1.22.10": {
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"dependencies": [
"is-core-module",
"path-parse",
"supports-preserve-symlinks-flag"
],
"bin": true
},
"restore-cursor@3.1.0": {
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"dependencies": [
"onetime",
"signal-exit"
]
},
"reusify@1.1.0": {
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
},
"run-parallel@1.2.0": {
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dependencies": [
"queue-microtask"
]
},
"safe-buffer@5.2.1": {
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"sass-lookup@6.1.0": {
"integrity": "sha512-Zx+lVyoWqXZxHuYWlTA17Z5sczJ6braNT2C7rmClw+c4E7r/n911Zwss3h1uHI9reR5AgHZyNHF7c2+VIp5AUA==",
"dependencies": [
"commander@12.1.0",
"enhanced-resolve"
],
"bin": true
},
"semver@7.7.2": {
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"bin": true
@@ -302,18 +1060,126 @@
],
"scripts": true
},
"signal-exit@3.0.7": {
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"simple-swizzle@0.2.2": {
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
"dependencies": [
"is-arrayish"
]
},
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
"source-map@0.6.1": {
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"stream-to-array@2.3.0": {
"integrity": "sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==",
"dependencies": [
"any-promise"
]
},
"string_decoder@1.3.0": {
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": [
"safe-buffer"
]
},
"stringify-object@3.3.0": {
"integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==",
"dependencies": [
"get-own-enumerable-property-symbols",
"is-obj",
"is-regexp"
]
},
"strip-ansi@6.0.1": {
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": [
"ansi-regex"
]
},
"strip-bom@3.0.0": {
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="
},
"strip-json-comments@2.0.1": {
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="
},
"stylus-lookup@6.1.0": {
"integrity": "sha512-5QSwgxAzXPMN+yugy61C60PhoANdItfdjSEZR8siFwz7yL9jTmV0UBKDCfn3K8GkGB4g0Y9py7vTCX8rFu4/pQ==",
"dependencies": [
"commander@12.1.0"
],
"bin": true
},
"supports-color@7.2.0": {
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dependencies": [
"has-flag"
]
},
"supports-preserve-symlinks-flag@1.0.0": {
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
},
"tapable@2.2.2": {
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="
},
"to-regex-range@5.0.1": {
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": [
"is-number"
]
},
"ts-api-utils@2.1.0_typescript@5.8.3": {
"integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==",
"dependencies": [
"typescript"
]
},
"ts-graphviz@2.1.6": {
"integrity": "sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==",
"dependencies": [
"@ts-graphviz/adapter",
"@ts-graphviz/ast",
"@ts-graphviz/common",
"@ts-graphviz/core"
]
},
"tsconfig-paths@4.2.0": {
"integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
"dependencies": [
"json5",
"minimist",
"strip-bom"
]
},
"tslib@2.8.1": {
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"typescript@5.8.3": {
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"bin": true
},
"undici-types@6.21.0": {
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
},
"util-deprecate@1.0.2": {
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"walkdir@0.4.1": {
"integrity": "sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ=="
},
"wcwidth@1.0.1": {
"integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
"dependencies": [
"defaults"
]
},
"wrappy@1.0.2": {
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"zod@4.0.5": {
"integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA=="
}

View File

@@ -13,9 +13,7 @@ export const route = createHonoRoute('/img');
async function convertImage(query: {cropSquare?: boolean | undefined;width?: number | undefined;height?: number | undefined;}, data: Uint8Array<ArrayBufferLike>): Promise<Uint8Array<ArrayBufferLike>> {
const image = sharp(data);
const rootMetadata = await image.metadata();
const rootWidth = rootMetadata.width;
const rootHeight = rootMetadata.height;
const squareSize = Math.min(rootWidth, rootHeight);
const squareSize = Math.min(rootMetadata.width, rootMetadata.height);
if (query.cropSquare) image.resize(squareSize, squareSize);
const newImage = sharp(await image.png().toBuffer());
@@ -34,8 +32,8 @@ const imgNameParamSchema = z.object({
});
const imgQuerySchema = z.object({
cropSquare: z.coerce.boolean().optional(),
width: z.coerce.number().max(3840).optional(),
height: z.coerce.number().max(2160).optional(),
width: z.coerce.number().min(64).max(3840).optional(),
height: z.coerce.number().min(64).max(2160).optional(),
});
route.app.get('/:imgName',
typedZValidator('param', imgNameParamSchema),

View File

@@ -37,6 +37,7 @@ interface MessageBase {
export class ProfileMessageManager extends ProfileContentManager {
// deno-lint-ignore require-await
async getMessages() {
return [];
}

View File

@@ -15,6 +15,7 @@ interface ProfileReputation {
export class ProfileReputationManager extends ProfileContentManager {
// deno-lint-ignore require-await
async export(): Promise<ProfileReputation> {
return {
AccountId: this.profile.getId(),

View File

@@ -25,7 +25,7 @@ export class ProfileSettingsManager extends ProfileContentManager {
else return [];
}
async getSetting(setting: ProfileSetting) {
async getSetting(setting: string) {
const settings = await this.getAllSettings();
return settings.find(s => s.Key == setting)?.Value || null;
}

View File

@@ -5,6 +5,7 @@ import { SelfAccount, type RecNetAccount } from "./types/profile.ts";
import Command from "./../commands/command.ts";
import z from "zod";
import { PlatformMask, PlatformType, ProfileRole } from "../platforms/types.ts";
import Logging from "@proxnet/undead-logging";
const profiles: Map<number, Profile> = new Map();
@@ -13,6 +14,8 @@ class ProfileManagerBase extends ServerContentBase {
static profilesKey = "profiles";
static profileByNameKey = "profileName";
#log = new Logging("ProfileManager");
async #getUnusedId() {
let id = Math.round(Math.random() * 2_147_483_647);
if (await this.get(id)) id = await this.#getUnusedId();
@@ -136,6 +139,7 @@ class ProfileManagerBase extends ServerContentBase {
})
]
}));
this.#log.i('Profile manager initialized');
}
}

View File

@@ -1,26 +1,47 @@
import Logging from "@proxnet/undead-logging";
import { ServerContentBase } from "../ContentBase.ts";
import KV from "../persistence/kv.ts";
import type Profile from "../profiles/profile.ts";
import { RoomFactory } from "./internal/RoomFactory.ts";
import { FactoryMode } from "./internal/RoomDataTypes.ts";
export class ServerRoomsBase extends ServerContentBase {
#roomsKey = "rooms";
#roomNamesKey = "room_names"
#subroomKv = new KV('subrooms', true);
#log = new Logging("Rooms");
static roomNamesKey = "room_names";
static playerDormsKey = "dorms";
protected override async start() {
await this.#subroomKv.init();
this.#log.i('[sub]rooms database initialized');
}
getKv() {
return this.kv;
}
async getIdFromName(name: string) {
const id = await this.kv.getKv().get<number>([this.#roomsKey, name]);
const id = await this.kv.getKv().get<number>([ServerRoomsBase.roomNamesKey, name]);
if (id.value == null) return null;
return id.value;
}
async getPlayerDorm(profile: Profile) {
const id = await this.kv.getKv().get<number>([ServerRoomsBase.playerDormsKey, profile.getId()]);
if (id.value == null) return null;
else return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id.value });
}
async getFromName(name: string) {
const id = await this.getIdFromName(name);
if (id == null) return null;
return await this.get(id);
}
async get(id: number) {
return await new RoomFactory(this.server, this.kv).init({ mode: FactoryMode.Fetch, id: id });
}
}

View File

@@ -0,0 +1,58 @@
import { RoomAccessibility } from "./RoomDataTypes.ts";
export interface AGRoomScene {
Name: string,
RoomSceneLocationId: string,
IsSandbox: boolean,
CanMatchmakeInto: boolean,
SupportsJoinInProgress: boolean,
UseLevelBasedMatchmaking: boolean,
UseAgeBasedMatchmaking: boolean,
UseRecRoyaleMatchmaking: boolean,
MaxPlayers: number
}
export interface AGRoom {
Name: string,
Description: string,
Accessibility: RoomAccessibility,
SupportsLevelVoting: boolean,
CloningAllowed: boolean,
SupportsScreens: boolean,
SupportsWalkVR: boolean,
SupportsTeleportVR: boolean,
Scenes: AGRoomScene[]
}
export enum AGRoomReleaseStatus {
EditorOnly,
TestServersOnly,
Released
}
export interface AGRoomLocation {
Name: string,
ReplicationId: string,
SceneName: string,
RequiredSubSceneNames: string[],
LevelRoomSubSceneNames: string[],
SupportsVRLow: boolean,
SupportsMobile: boolean,
MaxPlayers: number,
ReleaseStatus: AGRoomReleaseStatus,
EmptyOnSandboxClone: boolean,
SupportedGameMode: number,
GameTeamColorSettings: {
TeamOutfitColorEmissionEnabled: boolean,
TeamOutfitColorEmissionAmount: number,
CustomTeamColors: {
Team: number,
Color: {
r: number,
g: number,
b: number,
a: number
}
}[]
}
}

View File

@@ -1,7 +1,3 @@
import { RoomLocation } from "../../instances/base.ts";
import { RoomFactory } from "./RoomFactory.ts";
import { SubroomFactory } from "./SubroomFactory.ts";
export enum WriteMode {
Overwrite = "overwrite",
WriteIfFree = "if_free"
@@ -12,8 +8,11 @@ export enum FactoryMode {
Write = 'write'
}
export type HardwareSupport = "screens" | "walk_vr" | "teleport_vr" | "low_vr" | "mobile";
export const HardwareSupportStrings = ["screens", "walk_vr", "teleport_vr", "low_vr", "mobile"];
export const HardwareSupports = ["screens", "walk_vr", "teleport_vr", "low_vr", "mobile"] as const;
export type HardwareSupport = typeof HardwareSupports[number];
export const GalvanicRoomTags = ["recroomoriginal", "dormroom", "paintball", "screen", "walkvr", "teleportvr", "junior"] as const;
export type GalvanicRoomTag = typeof GalvanicRoomTags[number];
export enum RoomState {
Active,
@@ -29,30 +28,6 @@ export enum RoomAccessibility {
Unlisted
}
export interface BuiltinScene {
Name: string,
RoomSceneLocationId: RoomLocation,
IsSandbox: boolean,
CanMatchmakeInto: boolean,
SupportsJoinInProgress: boolean,
UseLevelBasedMatchmaking: boolean,
UseAgeBasedMatchmaking: boolean,
UseRecRoyaleMatchmaking: boolean,
MaxPlayers: number
}
export interface BuiltinRoom {
Name: string,
Description: string,
Accessibility: RoomAccessibility,
SupportsLevelVoting: boolean,
CloningAllowed: boolean,
SupportsScreens: boolean,
SupportsWalkVR: boolean,
SupportsTeleportVR: boolean,
Scenes: BuiltinScene[]
}
export interface RoomScene {
RoomSceneId: number,
RoomId: number,
@@ -65,24 +40,15 @@ export interface RoomScene {
DataModifiedAt: string
}
export interface Room {
export interface Room extends DatabaseRoomContent {
RoomId: number,
Name: string,
Description: string,
CreatorPlayerId: number,
ImageName: string,
State: RoomState,
Accessibility: RoomAccessibility,
SupportsLevelVoting: boolean,
IsAGRoom: boolean,
IsDormRoom?: boolean,
CloningAllowed: boolean,
SupportsScreens: boolean,
SupportsWalkVR: boolean,
SupportsTeleportVR: boolean,
SupportsMobile: boolean,
SupportsVRLow: boolean,
AllowsJuniors: boolean,
RoomWarningMask: number,
CustomRoomWarning: string,
DisableMicAutoMute?: boolean | null
}
@@ -103,6 +69,10 @@ export enum TagType {
Banned
}
export interface GalvanicTagDTO {
Tag: GalvanicRoomTag,
Type: TagType
}
export interface TagDTO {
Tag: string,
Type: TagType
@@ -120,7 +90,7 @@ export interface RoomDetails {
CheerCount: number,
FavoriteCount: number,
VisitCount: number,
Tags: TagDTO[]
Tags: (GalvanicTagDTO | TagDTO)[]
}
export enum CreateModifyRoomStatus {
@@ -150,29 +120,48 @@ export enum CreateModifyRoomStatus {
PlayerIsRoomBanned = 410
}
export interface DatabaseRoomContent {
Name: string,
Description: string,
CreatorPlayerId: number,
ImageName: string,
State: RoomState,
Accessibility: RoomAccessibility,
SupportsLevelVoting: boolean,
IsAGRoom: boolean,
IsDormRoom: boolean,
CloningAllowed: boolean,
AllowsJuniors: boolean,
RoomWarningMask: number,
CustomRoomWarning: string,
DisableMicAutoMute?: boolean | null
}
export interface DatabaseRoom {
Visits: number,
Hardware: Set<HardwareSupport>,
Subrooms: number[],
Tags: Set<string>,
Tags: Set<number>,
CoOwners: Set<number>,
InvitedCoOwners: Set<number>,
Hosts: Set<number>,
InvitedHosts: Set<number>,
Favorites: Set<number>,
Cheers: Set<number>
Room: DatabaseRoomContent
}
export interface DatabaseSubroom {
RoomId: number,
RoomSceneLocationId: string,
Name: string,
IsSandbox: boolean,
MaxPlayers: number,
CanMatchmakeInto: boolean,
LatestSaveId: number,
Saves: RoomSave[]
}
export interface RoomUpdatedEvent {
room: RoomFactory
export interface RoomSave {
SaveId: number,
DataBlobName: string,
SavedAt: string
}
export interface SubroomUpdatedEvent {
subroom: SubroomFactory
}
export * as RoomDataTypes from "./DataTypes.ts";
export * as RoomDataTypes from "./RoomDataTypes.ts";

View File

@@ -0,0 +1,10 @@
import { RoomFactory } from "./RoomFactory.ts";
import { SubroomFactory } from "./SubroomFactory.ts";
export interface RoomUpdatedEvent {
room: RoomFactory
}
export interface SubroomUpdatedEvent {
subroom: SubroomFactory
}

View File

@@ -1,53 +1,273 @@
import Logging from "@proxnet/undead-logging";
import type KV from "../../persistence/kv.ts";
import { type ServerBase } from "../../server.ts";
import { DatabaseRoom, RoomDataTypes } from "./DataTypes.ts";
import { DatabaseRoom, FactoryMode, GalvanicTagDTO, RoomDataTypes, TagDTO, TagType, WriteMode } from "./RoomDataTypes.ts";
import { SubroomFactory } from "./SubroomFactory.ts";
import { ServerRoomsBase } from "../base.ts";
export interface RoomFactoryOptions {
const log = new Logging("RoomFactory");
const roomDebug = true;
interface FactoryOptionsBase {
mode: FactoryMode,
id?: number,
name?: string,
name?: string
}
export interface WriteFactoryOptions extends FactoryOptionsBase {
mode: FactoryMode.Write,
writeMode: WriteMode,
}
export interface FetchFactoryOptions extends FactoryOptionsBase {
mode: FactoryMode.Fetch
}
export type FactoryOptions = WriteFactoryOptions | FetchFactoryOptions;
export class RoomFactory {
static roomsKey = "rooms";
#server: ServerBase;
#kv: KV;
#roomId: number | undefined;
#name: string | undefined;
#description: string | undefined;
#creatorPlayerId: number | undefined;
#imageName: string | undefined;
#state: RoomDataTypes.RoomScene | undefined;
#accessibility: RoomDataTypes.RoomAccessibility | undefined;
#supportsLevelVoting: boolean | undefined;
#isAGRoom: boolean | undefined;
#isDormRoom: boolean | undefined;
#cloningAllowed: boolean | undefined;
#supportsScreens: boolean | undefined;
#supportsWalkVR: boolean | undefined;
#supportsTeleportVR: boolean | undefined;
#allowsJuniors: boolean | undefined;
#roomWarningMask: number | undefined;
#customRoomWarning: string | undefined;
#disableMicAutoMute: boolean | undefined;
#dbMeta: DatabaseRoom | null = null;
factoryMode: FactoryMode = FactoryMode.Fetch;
writeMode: WriteMode = WriteMode.WriteIfFree;
#visits: number | undefined;
#hardware
#obj: DatabaseRoom | null = null;
#hardwareSupport: Set<RoomDataTypes.HardwareSupport> | null = null;
#tags: Set<string> | null = null;
#cannotAccessBeforeInitError = new Error("Cannot access properties before initialization");
#cannotWriteBeforeInitError = new Error("Cannot write before initialization");
constructor(server: ServerBase, kv: KV) {
this.#server = server;
this.#kv = kv;
}
async init(options: RoomFactoryOptions) {
async init(options: FactoryOptions) {
if (typeof options.id == 'undefined' && typeof options.name == 'undefined')
throw new Error("Must specify a room ID or a room name");
if (typeof options.id !== 'undefined') this.#roomId = options.id;
else {
if (!options.name) throw new Error("Room name must be provided");
const id = await this.#server.Rooms.getIdFromName(options.name);
if (id == null) throw new Error("Room name not found");
this.#roomId = id;
}
const obj = await this.#kv.getKv().get<DatabaseRoom>([RoomFactory.roomsKey, this.#roomId, 'meta']);
if (obj.value == null) return null;
else this.#obj = obj.value;
const hardwareSupport = await this.#kv.getKv().get<Set<RoomDataTypes.HardwareSupport>>([RoomFactory.roomsKey, this.#roomId, 'hardware']);
if (hardwareSupport.value == null) return null;
else this.#hardwareSupport = hardwareSupport.value;
const tags = await this.#kv.getKv().get<Set<string>>([RoomFactory.roomsKey, this.#roomId, 'tags']);
this.#tags = tags.value;
this.factoryMode = options.mode;
if (options.mode == FactoryMode.Write) this.writeMode = options.writeMode;
return this;
}
async write() {
if (typeof this.#roomId !== 'number') throw this.#cannotWriteBeforeInitError;
const w = async () => {
if (typeof this.#roomId !== 'number') throw this.#cannotWriteBeforeInitError;
await Promise.all([
this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'meta'], this.#obj),
this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'hardware'], this.#hardwareSupport),
this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'tags'], this.#tags),
]);
if (!this.IsDormRoom) this.#kv.getKv().set([ServerRoomsBase.roomNamesKey, this.Name], this.#roomId);
else this.#kv.getKv().set([ServerRoomsBase.playerDormsKey, this.CreatorPlayerId], this.#roomId);
this.#server.emit('room.updated', { room: this });
if (roomDebug) log.d(`Room ${this.#roomId} written and emitted`);
}
if (this.factoryMode == FactoryMode.Fetch) throw new Error("Cannot write in fetch mode");
const key = [RoomFactory.roomsKey, this.#roomId, 'meta'];
const val = await this.#kv.getKv().get(key);
if (val == null) await w();
else {
if (this.writeMode == WriteMode.Overwrite) await w();
else throw new Error("Room already exists");
}
}
async export(): Promise<RoomDataTypes.RoomDetails> {
if (!this.#obj || !this.#roomId) throw this.#cannotAccessBeforeInitError;
const obj = this.#obj;
const hardwareSupport = this.getHardwareSupport();
const autoTags = this.getTags();
const galvTags = this.getGalvanicTags();
const subroomExports = (await Promise.all(
this.getSubrooms().map(subroom => this.getSubroom(subroom))
)).map(factory => factory.export());
return {
Room: {
RoomId: this.#roomId,
Name: obj.Room.Name,
Description: obj.Room.Description,
CreatorPlayerId: obj.Room.CreatorPlayerId,
ImageName: obj.Room.ImageName,
State: obj.Room.State,
Accessibility: obj.Room.Accessibility,
SupportsLevelVoting: obj.Room.SupportsLevelVoting,
IsAGRoom: obj.Room.IsAGRoom,
IsDormRoom: obj.Room.IsDormRoom,
CloningAllowed: obj.Room.CloningAllowed,
AllowsJuniors: obj.Room.AllowsJuniors,
RoomWarningMask: obj.Room.RoomWarningMask,
CustomRoomWarning: obj.Room.CustomRoomWarning,
DisableMicAutoMute: obj.Room.DisableMicAutoMute,
SupportsScreens: hardwareSupport.has('screens'),
SupportsWalkVR: hardwareSupport.has('walk_vr'),
SupportsTeleportVR: hardwareSupport.has('teleport_vr'),
SupportsMobile: hardwareSupport.has('mobile'),
SupportsVRLow: hardwareSupport.has('low_vr')
},
Scenes: subroomExports,
Tags: autoTags.concat(galvTags),
CoOwners: [],
InvitedCoOwners: [],
Hosts: [],
InvitedHosts: [],
CheerCount: 0,
FavoriteCount: 0,
VisitCount: await this.getVisitCount()
}
}
getSubrooms() {
if (!this.#obj) throw this.#cannotAccessBeforeInitError;
return this.#obj.Subrooms;
}
async getSubroom(id: number) {
return await new SubroomFactory(this.#server, this.#kv).init({ mode: FactoryMode.Fetch, writeMode: WriteMode.WriteIfFree, id });
}
get Name() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Name }
set Name(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.Name = data }
get Description() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Description }
set Description(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.Description = data }
get CreatorPlayerId() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.CreatorPlayerId }
set CreatorPlayerId(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.CreatorPlayerId = data }
get ImageName() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.ImageName }
set ImageName(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.ImageName = data }
get State() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.State }
set State(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.State = data }
get RoomAccessibility() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.Accessibility }
set RoomAccessibility(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.Accessibility = data }
get SupportsLevelVoting() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.SupportsLevelVoting }
set SupportsLevelVoting(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.SupportsLevelVoting = data }
get IsAGRoom() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.IsAGRoom }
set IsAGRoom(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.IsAGRoom = data }
get IsDormRoom() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.IsDormRoom }
set IsDormRoom(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.IsDormRoom = data }
get CloningAllowed() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.CloningAllowed }
set CloningAllowed(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.CloningAllowed = data }
get AllowsJuniors() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.AllowsJuniors }
set AllowsJuniors(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.AllowsJuniors = data }
get RoomWarningMask() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.RoomWarningMask }
set RoomWarningMask(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.RoomWarningMask = data }
get CustomRoomWarning() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.CustomRoomWarning }
set CustomRoomWarning(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.CustomRoomWarning = data }
get DisableMicAutoMute() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Room.DisableMicAutoMute }
set DisableMicAutoMute(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; this.#obj.Room.DisableMicAutoMute = data }
getHardwareSupport() {
if (!this.#hardwareSupport) throw this.#cannotAccessBeforeInitError;
return this.#hardwareSupport;
}
async #saveHardwareSupport() {
if (!this.#roomId) throw this.#cannotAccessBeforeInitError;
await this.#kv.getKv().set([this.#roomId, 'hardware'], this.#hardwareSupport);
}
async addHardwareSupport(...hardware: RoomDataTypes.HardwareSupport[]) {
if (!this.#hardwareSupport || !this.#roomId) throw this.#cannotAccessBeforeInitError;
if (typeof hardware == 'string') {
this.#hardwareSupport.add(hardware);
await this.#saveHardwareSupport();
} else {
for (const hw of hardware) this.#hardwareSupport.add(hw);
await this.#saveHardwareSupport();
}
}
async removeHardwareSupport(hardware: RoomDataTypes.HardwareSupport) {
if (!this.#hardwareSupport) throw this.#cannotAccessBeforeInitError;
this.#hardwareSupport.delete(hardware);
await this.#saveHardwareSupport();
}
/**
* Visits are fetched during every access, not during init
*/
async getVisitCount() {
if (!this.#roomId) throw this.#cannotAccessBeforeInitError;
const val = await this.#kv.getKv().get<number>([RoomFactory.roomsKey, this.#roomId, 'visits']);
if (val.value == null) return 0;
else return val.value;
}
/**
* Visits are fetched during every access, not during init
*/
async addVisit() {
if (!this.#roomId) throw this.#cannotAccessBeforeInitError;
const visits = await this.getVisitCount();
await this.#kv.getKv().set([RoomFactory.roomsKey, this.#roomId, 'visits'], visits + 1);
}
getGalvanicTags(): GalvanicTagDTO[] {
const tags: GalvanicTagDTO[] = [];
if (this.IsAGRoom) tags.push({ Tag: "recroomoriginal", Type: TagType.AGOnly });
if (this.IsDormRoom) tags.push({ Tag: "dormroom", Type: TagType.Auto });
if (this.Name === "Paintball" || this.Name === "PaintballVR") tags.push({ Tag: "paintball", Type: TagType.Auto });
const hardwareSupport = this.getHardwareSupport();
if (hardwareSupport.has('screens')) tags.push({ Tag: "screen", Type: TagType.Auto });
if (hardwareSupport.has('walk_vr')) tags.push({ Tag: "walkvr", Type: TagType.Auto });
if (hardwareSupport.has('teleport_vr')) tags.push({ Tag: "teleportvr", Type: TagType.Auto });
if (this.AllowsJuniors) tags.push({ Tag: "junior", Type: TagType.Auto });
return tags;
}
getTags(): TagDTO[] {
if (!this.#tags) return [];
return this.#tags.values().toArray().map(val => ({ Tag: val, Type: TagType.General }));
}
setTags(tags: Set<string>) {
this.#tags = tags;
}
}

View File

@@ -0,0 +1,121 @@
import type KV from "../../persistence/kv.ts";
import { type ServerBase } from "../../server.ts";
import { DatabaseSubroom, FactoryMode, RoomDataTypes, RoomSave, WriteMode } from "./RoomDataTypes.ts";
export interface SubroomFactoryOptions {
mode: FactoryMode,
writeMode: WriteMode
id: number
}
export class SubroomFactory {
#server: ServerBase;
#kv: KV;
#subroomId: number | null = null;
factoryMode: FactoryMode = FactoryMode.Fetch;
writeMode: WriteMode = WriteMode.WriteIfFree;
#obj: DatabaseSubroom | null = null;
#saves: RoomSave[] | null = null;
#cannotAccessBeforeInitError = new Error("Cannot access properties before initialization");
#cannotWriteBeforeInitError = new Error("Cannot write before initialization");
constructor(server: ServerBase, kv: KV) {
this.#server = server;
this.#kv = kv;
}
async init(options: SubroomFactoryOptions) {
const data = await this.#kv.getKv().get<DatabaseSubroom>([options.id, 'meta']);
if (data == null && this.factoryMode == FactoryMode.Fetch) throw new Error("No such subroom");
const saves = await this.#kv.getKv().get<RoomSave[]>([options.id, 'saves']);
this.#saves = saves.value;
this.#obj = data.value;
this.#subroomId = options.id;
return this;
}
async write() {
if (!this.#obj) throw this.#cannotWriteBeforeInitError;
if (!this.#subroomId) throw new Error("No RoomSceneId set");
await this.#kv.getKv().set([this.#subroomId, 'meta'], this.#obj);
await this.#kv.getKv().set([this.#subroomId, 'saves'], this.#saves);
this.#server.emit('room.subroom.updated', { subroom: this });
}
export(): RoomDataTypes.RoomScene {
if (!this.#subroomId) throw this.#cannotAccessBeforeInitError;
const save = this.getLatestSave();
if (!save) throw new Error("No save to export");
return {
RoomId: this.RoomId,
RoomSceneId: this.#subroomId,
RoomSceneLocationId: this.RoomSceneLocationId,
Name: this.Name,
IsSandbox: this.IsSandbox,
MaxPlayers: this.MaxPlayers,
CanMatchmakeInto: this.CanMatchmakeInto,
DataModifiedAt: save.SavedAt,
DataBlobName: save.DataBlobName
}
}
get RoomId() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.RoomId; }
set RoomId(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.RoomId = data }
get RoomSceneId() { if (!this.#subroomId) throw this.#cannotAccessBeforeInitError; else return this.#subroomId; }
get RoomSceneLocationId() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.RoomSceneLocationId; }
set RoomSceneLocationId(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.RoomSceneLocationId = data }
get Name() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.Name; }
set Name(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.Name = data }
get IsSandbox() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.IsSandbox; }
set IsSandbox(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.IsSandbox = data }
get MaxPlayers() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.MaxPlayers; }
set MaxPlayers(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.MaxPlayers = data }
get CanMatchmakeInto() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.CanMatchmakeInto; }
set CanMatchmakeInto(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.CanMatchmakeInto = data }
get LatestSaveId() { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else return this.#obj.LatestSaveId; }
set LatestSaveId(data) { if (!this.#obj) throw this.#cannotAccessBeforeInitError; else this.#obj.LatestSaveId = data }
#getAvailableSaveId() {
if (!this.#saves) throw this.#cannotAccessBeforeInitError;
let newId = Math.round(Math.random() * Math.pow(2, 31));
while (this.#saves.some(save => save.SaveId == newId)) newId = this.#getAvailableSaveId();
return newId;
}
addSave(dataBlobName: string) {
if (!this.#saves) throw this.#cannotAccessBeforeInitError;
this.#saves.push({
SaveId: this.#getAvailableSaveId(),
DataBlobName: dataBlobName,
SavedAt: new Date().toISOString()
});
}
getSaves() {
if (!this.#saves) throw this.#cannotAccessBeforeInitError;
return this.#saves;
}
getLatestSave() {
if (!this.#saves) throw this.#cannotAccessBeforeInitError;
return this.#saves.find(save => save.SaveId == this.LatestSaveId);
}
}

View File

@@ -11,7 +11,7 @@ import { type ProfileUpdateEvent } from "./profiles/events/ProfileUpdate.ts";
import { type RoleUpdateEvent } from "./profiles/events/RoleUpdate.ts";
import ProfileManagerBase from "./profiles/manager.ts";
import { ServerRoomsBase } from "./rooms/base.ts";
import { RoomUpdatedEvent, SubroomUpdatedEvent } from "./rooms/internal/DataTypes.ts";
import { RoomUpdatedEvent, SubroomUpdatedEvent } from "./rooms/internal/RoomEvents.ts";
interface ServerEvents {
'profile.roleupdate': RoleUpdateEvent,
@@ -32,6 +32,10 @@ class ServerBase extends EventManager<ServerEvents> {
Instances = new InstanceManager(this, 'instances');
Content = new ServerContentManager(this, "content");
Rooms = new ServerRoomsBase(this, 'rooms', true);
generateMask(...num: number[]) {
return num.reduce((sum, val) => sum + val, 0);
}
}
const Server = new ServerBase();

View File

@@ -50,14 +50,18 @@ export default class SocketConsoleHandler {
async #onMsg(ev: MessageEvent) {
try {
const parsed = JSON.parse(ev.data) as ConsoleItem;
const zodd = ConsoleItemSchema.safeParse(parsed);
if (!zodd.success) this.destroy();
else if (zodd.data.e == ConsoleEvent.Command) {
const data = await Server.Commands.dispatch(...zodd.data.d.split(' '));
const zodParsed = ConsoleItemSchema.safeParse(parsed);
if (!zodParsed.success) this.destroy();
else if (zodParsed.data.e == ConsoleEvent.Command) {
const data = await Server.Commands.dispatch(...zodParsed.data.d.split(' '));
if (data instanceof Error) throw data;
this.send(ConsoleEvent.Message, chalk.gray(`> ${chalk.yellow(data)}`));
}
else if (zodd.data.e == ConsoleEvent.Close) this.destroy();
else if (zodParsed.data.e == ConsoleEvent.Close) this.destroy();
} catch (err) {
this.#log.e(err);
}

View File

@@ -3,6 +3,7 @@ import {
CompletionMessage,
Message,
MessageKind,
NotificationHandler,
PushNotificationId,
SignalMessageType,
SignalRMessage,
@@ -164,7 +165,7 @@ export class SignalRSocketHandler {
}
}
sendNotification(id: PushNotificationId | string, args?: object) {
sendNotification(id: PushNotificationId | NotificationHandler, args?: object) {
const msg: SignalRMessage = {
type: SignalMessageType.Invocation,
target: "Notification",

View File

@@ -182,4 +182,20 @@ export enum PushNotificationId {
CommunityBoardUpdate = 95,
CommunityBoardAnnouncementUpdate,
InventionModerationStateChanged = 100,
}
}
const notificationHandlers = [
"AccountUpdate",
"SelfAccountUpdate",
"AnnouncementUpdate",
"AnnouncementDelete",
"ChatMessageReceived",
"CommerceSubscriptionUpdate",
"RoomInstanceUpdate",
"PresenceUpdate",
"ModerationQuitGame",
"AppVersionUpdate",
"PlayerProgressionLevelUpdate",
"ReputationUpdate"
] as const;
export type NotificationHandler = typeof notificationHandlers[number];

View File

@@ -1,5 +1,5 @@
import { Context, Next } from "@hono/hono";
import { HonoEnv } from "./types.ts";
import { type HonoEnv } from "./types.ts";
import Logging from "@proxnet/undead-logging";
import z from "zod";
import { verify } from "@hono/hono/jwt";

View File

@@ -1,23 +1,24 @@
import { zValidator } from "@hono/zod-validator";
import type { MiddlewareHandler } from "@hono/hono";
import { z, ZodObject } from "zod";
import { z } from "zod";
import type { HonoEnv } from "./types.ts";
import { ZodSchema } from "zod/v4";
// thanks claude, this hurt my brain!
export const typedZValidator = <
Target extends 'query' | 'json' | 'form' | 'header' | 'param' | 'cookie',
Schema extends ZodObject
T extends 'query' | 'json' | 'form' | 'header' | 'param' | 'cookie',
S extends ZodSchema
>(
target: Target,
schema: Schema
target: T,
schema: S
) => {
return zValidator(target, schema) as MiddlewareHandler<
HonoEnv,
string,
{
in: { [K in Target]: z.input<Schema> };
out: { [K in Target]: z.output<Schema> };
in: { [K in T]: z.input<S> };
out: { [K in T]: z.output<S> };
}
>;
};