diff --git a/deno.json b/deno.json index 4c93d8a..eff550b 100644 --- a/deno.json +++ b/deno.json @@ -12,7 +12,7 @@ "@std/assert": "jsr:@std/assert@1", "@std/dotenv": "jsr:@std/dotenv@^0.225.5", "chalk": "npm:chalk@^5.6.2", - "sharp": "npm:sharp@^0.34.3", + "imagescript": "npm:imagescript@^1.3.1", "zod": "npm:zod@^4.0.5" }, "nodeModulesDir": "auto" diff --git a/deno.lock b/deno.lock index a14f1f0..ca7938c 100644 --- a/deno.lock +++ b/deno.lock @@ -17,10 +17,9 @@ "jsr:@std/internal@^1.0.9": "1.0.10", "jsr:@std/path@1": "1.1.2", "jsr:@std/path@^1.1.1": "1.1.2", - "npm:@types/node@*": "24.2.0", "npm:chalk@^5.3.0": "5.6.0", "npm:chalk@^5.6.2": "5.6.2", - "npm:sharp@~0.34.3": "0.34.3", + "npm:imagescript@^1.3.1": "1.3.1", "npm:zod@^4.0.5": "4.1.5" }, "jsr": { @@ -91,240 +90,14 @@ } }, "npm": { - "@emnapi/runtime@1.5.0": { - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", - "dependencies": [ - "tslib" - ] - }, - "@img/sharp-darwin-arm64@0.34.3": { - "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==", - "optionalDependencies": [ - "@img/sharp-libvips-darwin-arm64" - ], - "os": ["darwin"], - "cpu": ["arm64"] - }, - "@img/sharp-darwin-x64@0.34.3": { - "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==", - "optionalDependencies": [ - "@img/sharp-libvips-darwin-x64" - ], - "os": ["darwin"], - "cpu": ["x64"] - }, - "@img/sharp-libvips-darwin-arm64@1.2.0": { - "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==", - "os": ["darwin"], - "cpu": ["arm64"] - }, - "@img/sharp-libvips-darwin-x64@1.2.0": { - "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==", - "os": ["darwin"], - "cpu": ["x64"] - }, - "@img/sharp-libvips-linux-arm64@1.2.0": { - "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==", - "os": ["linux"], - "cpu": ["arm64"] - }, - "@img/sharp-libvips-linux-arm@1.2.0": { - "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==", - "os": ["linux"], - "cpu": ["arm"] - }, - "@img/sharp-libvips-linux-ppc64@1.2.0": { - "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==", - "os": ["linux"], - "cpu": ["ppc64"] - }, - "@img/sharp-libvips-linux-s390x@1.2.0": { - "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==", - "os": ["linux"], - "cpu": ["s390x"] - }, - "@img/sharp-libvips-linux-x64@1.2.0": { - "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==", - "os": ["linux"], - "cpu": ["x64"] - }, - "@img/sharp-libvips-linuxmusl-arm64@1.2.0": { - "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==", - "os": ["linux"], - "cpu": ["arm64"] - }, - "@img/sharp-libvips-linuxmusl-x64@1.2.0": { - "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==", - "os": ["linux"], - "cpu": ["x64"] - }, - "@img/sharp-linux-arm64@0.34.3": { - "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==", - "optionalDependencies": [ - "@img/sharp-libvips-linux-arm64" - ], - "os": ["linux"], - "cpu": ["arm64"] - }, - "@img/sharp-linux-arm@0.34.3": { - "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==", - "optionalDependencies": [ - "@img/sharp-libvips-linux-arm" - ], - "os": ["linux"], - "cpu": ["arm"] - }, - "@img/sharp-linux-ppc64@0.34.3": { - "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==", - "optionalDependencies": [ - "@img/sharp-libvips-linux-ppc64" - ], - "os": ["linux"], - "cpu": ["ppc64"] - }, - "@img/sharp-linux-s390x@0.34.3": { - "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==", - "optionalDependencies": [ - "@img/sharp-libvips-linux-s390x" - ], - "os": ["linux"], - "cpu": ["s390x"] - }, - "@img/sharp-linux-x64@0.34.3": { - "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==", - "optionalDependencies": [ - "@img/sharp-libvips-linux-x64" - ], - "os": ["linux"], - "cpu": ["x64"] - }, - "@img/sharp-linuxmusl-arm64@0.34.3": { - "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==", - "optionalDependencies": [ - "@img/sharp-libvips-linuxmusl-arm64" - ], - "os": ["linux"], - "cpu": ["arm64"] - }, - "@img/sharp-linuxmusl-x64@0.34.3": { - "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==", - "optionalDependencies": [ - "@img/sharp-libvips-linuxmusl-x64" - ], - "os": ["linux"], - "cpu": ["x64"] - }, - "@img/sharp-wasm32@0.34.3": { - "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==", - "dependencies": [ - "@emnapi/runtime" - ], - "cpu": ["wasm32"] - }, - "@img/sharp-win32-arm64@0.34.3": { - "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==", - "os": ["win32"], - "cpu": ["arm64"] - }, - "@img/sharp-win32-ia32@0.34.3": { - "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==", - "os": ["win32"], - "cpu": ["ia32"] - }, - "@img/sharp-win32-x64@0.34.3": { - "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==", - "os": ["win32"], - "cpu": ["x64"] - }, - "@types/node@24.2.0": { - "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", - "dependencies": [ - "undici-types" - ] - }, "chalk@5.6.0": { "integrity": "sha512-46QrSQFyVSEyYAgQ22hQ+zDa60YHA4fBstHmtSApj1Y5vKtG27fWowW03jCk5KcbXEWPZUIR894aARCA/G1kfQ==" }, "chalk@5.6.2": { "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==" }, - "color-convert@2.0.1": { - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": [ - "color-name" - ] - }, - "color-name@1.1.4": { - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "color-string@1.9.1": { - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": [ - "color-name", - "simple-swizzle" - ] - }, - "color@4.2.3": { - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dependencies": [ - "color-convert", - "color-string" - ] - }, - "detect-libc@2.0.4": { - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==" - }, - "is-arrayish@0.3.2": { - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, - "semver@7.7.2": { - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "bin": true - }, - "sharp@0.34.3": { - "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", - "dependencies": [ - "color", - "detect-libc", - "semver" - ], - "optionalDependencies": [ - "@img/sharp-darwin-arm64", - "@img/sharp-darwin-x64", - "@img/sharp-libvips-darwin-arm64", - "@img/sharp-libvips-darwin-x64", - "@img/sharp-libvips-linux-arm", - "@img/sharp-libvips-linux-arm64", - "@img/sharp-libvips-linux-ppc64", - "@img/sharp-libvips-linux-s390x", - "@img/sharp-libvips-linux-x64", - "@img/sharp-libvips-linuxmusl-arm64", - "@img/sharp-libvips-linuxmusl-x64", - "@img/sharp-linux-arm", - "@img/sharp-linux-arm64", - "@img/sharp-linux-ppc64", - "@img/sharp-linux-s390x", - "@img/sharp-linux-x64", - "@img/sharp-linuxmusl-arm64", - "@img/sharp-linuxmusl-x64", - "@img/sharp-wasm32", - "@img/sharp-win32-arm64", - "@img/sharp-win32-ia32", - "@img/sharp-win32-x64" - ], - "scripts": true - }, - "simple-swizzle@0.2.2": { - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": [ - "is-arrayish" - ] - }, - "tslib@2.8.1": { - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" - }, - "undici-types@7.10.0": { - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" + "imagescript@1.3.1": { + "integrity": "sha512-ue/zxSyEzj7je8Nlt2vjY9GEa2BbScFSRZJq7OTVDZFp0r57fyuxrlsF8qWgxTP+kP8WklTw4by/ZEYVX5S13w==" }, "zod@4.1.5": { "integrity": "sha512-rcUUZqlLJgBC33IT3PNMgsCq6TzLQEG/Ei/KTCU0PedSWRMAXoOUN+4t/0H+Q8bdnLPdqUYnvboJT0bn/229qg==" @@ -340,7 +113,7 @@ "jsr:@std/assert@1", "jsr:@std/dotenv@~0.225.5", "npm:chalk@^5.6.2", - "npm:sharp@~0.34.3", + "npm:imagescript@^1.3.1", "npm:zod@^4.0.5" ] } diff --git a/src/main.ts b/src/main.ts index 6bbcbf0..dc5fe21 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,11 +11,12 @@ import { SignalRSocketHandler } from "./server/socket/signalr/socket.ts"; import { PushNotificationId } from "./server/socket/signalr/types.ts"; import { genericResponse } from "./util/api.ts"; import { getNetConfig } from "./net.ts"; -import { PlatformType, TokenFormat, TokenType } from "./server/platforms/types.ts"; +import { PlatformMask, PlatformType, TokenFormat, TokenType } from "./server/platforms/types.ts"; import { HonoEnv } from "./util/types.ts"; import { Context } from "@hono/hono"; import { compress } from "@hono/hono/compress"; import { env } from "./env.ts"; +import { BitFlags } from "./util/flags.ts"; LoggingConfiguration.resetTimeFormat = TimeFormat.Unix; LoggingConfiguration.resetLogTiming = LogTiming.Microtask; @@ -115,7 +116,7 @@ const server = Deno.serve({ const profile = await Server.Profiles.get(payload.sub); if (!profile) return new Response("Internal Server Error (profile)", { status: 500 }); - const { response, socket } = Deno.upgradeWebSocket(req); + const { response, socket } = Deno.upgradeWebSocket(req, { idleTimeout: 60 }); const handler = new SignalRSocketHandler(socket, profile); gameSockets.add(handler); socket.onclose = () => { @@ -190,6 +191,6 @@ Server.Commands.addRootCommand(new Command({ Server.on('server.start', () => { Server.Profiles.get(1).then(prof => { - if (!prof) Server.Profiles.create(PlatformType.HeadlessBot, "", "Coach", 1); + if (!prof) Server.Profiles.create(PlatformType.HeadlessBot, new BitFlags(PlatformMask.All), "", "Coach", 1); }); }); \ No newline at end of file diff --git a/src/routes/accounts/routes/account.ts b/src/routes/accounts/routes/account.ts index 40df9d2..3b13571 100644 --- a/src/routes/accounts/routes/account.ts +++ b/src/routes/accounts/routes/account.ts @@ -3,10 +3,11 @@ import { RateLimiter, recNetResultResponse, statusResponse } from "../../../util import Server from "../../../server/server.ts"; import z from "zod"; import { transformCheckEnum, typedZValidator } from "../../../util/validators.ts"; -import { PlatformType } from "../../../server/platforms/types.ts"; +import { PlatformMask, PlatformType } from "../../../server/platforms/types.ts"; import Steam from "../../../util/steam/steam.ts"; import { HTTPStatus } from "@oneday/http-status"; import { accountRoute } from "./me/root.ts"; +import { BitFlags } from "../../../util/flags.ts"; export const route = createHonoRoute('/account'); @@ -50,7 +51,7 @@ route.app.post('/create', postCreateRateLimiter.middle(), typedZValidator('form' const cachedlogins = await Server.Platforms.getCachedLogins(form.platform, form.platformId, true); if (cachedlogins.length == 0) { - const profile = await Server.Profiles.create(form.platform, form.platformId, steam[0].realname ?? steam[0].personaname); + const profile = await Server.Profiles.create(form.platform, new BitFlags(PlatformMask.Steam), form.platformId, steam[0].realname ?? steam[0].personaname); if (!profile) return recNetResultResponse(c, HTTPStatus.OK, false, "Account could not be created"); Server.Content.steamAvatarDownloadForProfile(profile, steam[0].avatarfull); @@ -62,7 +63,7 @@ route.app.post('/create', postCreateRateLimiter.middle(), typedZValidator('form' } else { - const profile = await Server.Profiles.create(form.platform, form.platformId); + const profile = await Server.Profiles.create(form.platform, new BitFlags(PlatformMask.Steam), form.platformId); if (!profile) return recNetResultResponse(c, HTTPStatus.OK, false, "Account could not be created"); return c.json({ diff --git a/src/routes/accounts/routes/parentalcontrol.ts b/src/routes/accounts/routes/parentalcontrol.ts new file mode 100644 index 0000000..33d022d --- /dev/null +++ b/src/routes/accounts/routes/parentalcontrol.ts @@ -0,0 +1,8 @@ +import { authenticate } from "../../../util/api.ts"; +import { createHonoRoute } from "../../../util/import.ts"; + +export const route = createHonoRoute("/parentalcontrol"); + +route.app.get('/me', authenticate, c => { + return c.json({ accountId: c.get('profile').getId(), disallowInAppPurchases: false }); +}); \ No newline at end of file diff --git a/src/routes/api/routes/playerReputation.ts b/src/routes/api/routes/playerReputation.ts index 704f490..3d7f925 100644 --- a/src/routes/api/routes/playerReputation.ts +++ b/src/routes/api/routes/playerReputation.ts @@ -20,7 +20,7 @@ route.app.get('/v1/:id', authenticate, typedZValidator('param', getRepIdParamSch const getRepBulkBodySchema = z.object({ Ids: z.array(z.coerce.number()) }); -route.app.post('/v1/bulk', authenticate, typedZValidator('form', getRepBulkBodySchema), async c => { +route.app.post('/v1/bulk', typedZValidator('form', getRepBulkBodySchema), async c => { const ids = c.req.valid('form').Ids; if (typeof ids == 'object') { diff --git a/src/routes/api/routes/rooms.ts b/src/routes/api/routes/rooms.ts index dc836f4..9f12c52 100644 --- a/src/routes/api/routes/rooms.ts +++ b/src/routes/api/routes/rooms.ts @@ -3,6 +3,7 @@ import { createHonoRoute } from "../../../util/import.ts"; import { typedZValidator } from "../../../util/validators.ts"; import Server from "../../../server/server.ts"; import { authenticate } from "../../../util/api.ts"; +import { RoomAccessibility } from "../../../server/rooms/internal/RoomDataTypes.ts"; export const route = createHonoRoute("/rooms"); @@ -35,8 +36,9 @@ route.app.get('/v2/myrooms', authenticate, async c => { return c.json(rooms); }); -route.app.get('/v1/hot', authenticate, async c => { +route.app.get('/v1/hot', async c => { + // temporary! parse tags and manage "hot" rooms const agRoomIds = Server.Rooms.getAgRoomIds(); const factories = await Server.Rooms.getMany(...agRoomIds.values().toArray()); - return c.json((await Promise.all(factories.map(factory => factory.export()))).map(roomDetails => roomDetails.Room)); + return c.json((await Promise.all(factories.map(factory => factory.export()))).map(roomDetails => roomDetails.Room).filter(room => room.Accessibility == RoomAccessibility.Public)); }); \ No newline at end of file diff --git a/src/routes/auth/routes/connect.ts b/src/routes/auth/routes/connect.ts index 5a86c54..3e26f28 100644 --- a/src/routes/auth/routes/connect.ts +++ b/src/routes/auth/routes/connect.ts @@ -100,7 +100,8 @@ route.app.post('/token', typedZValidator('form', tokenGrantSchema), async c => { if (!profile) return error(TokenRequestError.AccessDenied); return c.json({ - token: await Server.Platforms.getToken(profile, TokenType.Access), + access_token: await Server.Platforms.getToken(profile, TokenType.Access), + refresh_token: await Server.Platforms.getToken(profile, TokenType.Refresh), }); } catch (err) { log.w(`Authentication error (token req): ${(err as Error).stack}`); @@ -112,12 +113,10 @@ route.app.post('/token', typedZValidator('form', tokenGrantSchema), async c => { const profile = await Server.Profiles.get(form.account_id); if (!profile) return error(TokenRequestError.InvalidRequest, "No such profile"); await Server.Platforms.updateLastLoginTime(form.platform, form.platform_id, form.account_id); - const accessToken = await Server.Platforms.getToken(profile, TokenType.Access); - const refreshToken = await Server.Platforms.getToken(profile, TokenType.Refresh); return c.json({ - access_token: accessToken, - refresh_token: refreshToken, + access_token: await Server.Platforms.getToken(profile, TokenType.Access), + refresh_token: await Server.Platforms.getToken(profile, TokenType.Refresh), }); } else return error(TokenRequestError.InvalidRequest, "No such profile"); }); \ No newline at end of file diff --git a/src/routes/img/base.ts b/src/routes/img/base.ts index f9a44c1..8376932 100644 --- a/src/routes/img/base.ts +++ b/src/routes/img/base.ts @@ -8,28 +8,51 @@ import Logging from "@proxnet/undead-logging"; import { statusResponse } from "../../util/api.ts"; import { HTTPStatus } from "@oneday/http-status"; import { Buffer } from "node:buffer"; -import sharp from "sharp"; +import { Image } from "imagescript"; export const route = createHonoRoute('/img'); +interface StaticImageRedirect { + org: string, + new: string +} + +const redirects: StaticImageRedirect[] = [ + { + org: "PaintballVR.png", + new: "Paintball.png" + } +]; + const log = new Logging("ImageRoute"); async function convertImage(query: ImageQuery, data: Uint8Array): Promise | null> { try { - const image = sharp(data); - const rootMetadata = await image.metadata(); - const squareSize = Math.min(rootMetadata.width, rootMetadata.height); - if (query.cropSquare) image.resize(squareSize, squareSize); + const image = await Image.decode(data); - const newImage = sharp(await image.png().toBuffer()); - if (query.width && query.height) - newImage.resize(query.width, query.height); + if (typeof query.height == 'number' && query.height >= image.height) query.height = image.height; + if (typeof query.width == 'number' && query.width >= image.width) query.width = image.width; + if (query.cropSquare) { + + if (image.height > image.width) image.cover(image.height, image.height); + else image.cover(image.width, image.width); + } + + if (query.width && query.height) { + if (image.height > image.width) { + image.resize(query.width, -1); + image.cover(query.width, query.height); + } else { + image.resize(-1, query.height); + image.cover(query.width, query.height); + } + } else if (query.width) - newImage.resize(query.width); + image.resize(query.width, -1); else if (query.height) - newImage.resize(undefined, query.height); + image.resize(-1, query.height); - return await newImage.png().toBuffer(); + return await image.encode(6); } catch (err) { log.w(`Image transformation failed: ${(err as Error).stack}`); return null; @@ -43,6 +66,7 @@ const imgQuerySchema = z.object({ cropSquare: z.coerce.boolean().optional(), width: z.coerce.number().min(64).max(3840).optional(), height: z.coerce.number().min(64).max(2160).optional(), + sig: z.literal('p1').optional() }); type ImageQuery = z.infer; @@ -51,19 +75,22 @@ route.app.get('/:imgName', typedZValidator('query', imgQuerySchema), async c => { - const { imgName } = c.req.valid('param'); + const params = c.req.valid('param'); const query = c.req.valid('query'); + const redirect = redirects.find(red => red.org === params.imgName); + if (redirect) params.imgName = redirect.new; + const datas: Uint8Array[] = (await Promise.all | null>([ new Promise(resolve => { - Deno.readFile(path.join(RootPath, "res/baseimg/", imgName)).then(img => { + Deno.readFile(path.join(RootPath, "res/baseimg/", params.imgName)).then(img => { resolve(img); }).catch(() => { resolve(null); }); }), new Promise(resolve => { - Server.Content.getFile(`img/${imgName}`).then(file => { + Server.Content.getFile(`img/${params.imgName}`).then(file => { if (file) resolve(file.Data); else resolve(null); }).catch(() => { @@ -72,6 +99,8 @@ route.app.get('/:imgName', }) ])).filter(val => val !== null); + if (query.sig) c.res.headers.set('content-signature', "key-id=KEY:RSA:p1.rec.net; data=aGk="); + if (datas.length == 0) return statusResponse(c, HTTPStatus.NotFound); else { const result = await convertImage(query, datas[0]); diff --git a/src/server/platforms/base.ts b/src/server/platforms/base.ts index 9f084b0..b20f85c 100644 --- a/src/server/platforms/base.ts +++ b/src/server/platforms/base.ts @@ -26,7 +26,7 @@ export class PlatformsManager extends ServerContentBase { async getToken(prof: Profile, type: TokenType) { const secret = env["SECRET"]; if (!secret) throw new Error("No SECRET in env. Did you forget to set it?"); - const exp = type == TokenType.Access ? Math.round(Date.now() / 1000) + 21_600 : Math.round(Date.now() / 1000) + 31_556_952; + const exp = type == TokenType.Access ? Math.round(Date.now() / 1000) + 21_600 : Math.round(Date.now() / 1000) + 21_600; const token: TokenFormat = { typ: type, diff --git a/src/server/platforms/types.ts b/src/server/platforms/types.ts index e0691d4..c405967 100644 --- a/src/server/platforms/types.ts +++ b/src/server/platforms/types.ts @@ -2,10 +2,8 @@ export enum TokenType { Access, Refresh } -export interface TokenFormatBase { +export interface TokenFormat { typ: TokenType -} -export interface TokenFormat extends TokenFormatBase { iss: string, exp: number, iat: number, diff --git a/src/server/profiles/content/Matchmaking.ts b/src/server/profiles/content/Matchmaking.ts index 6093f48..b01ce72 100644 --- a/src/server/profiles/content/Matchmaking.ts +++ b/src/server/profiles/content/Matchmaking.ts @@ -13,7 +13,7 @@ export class ProfileMatchmakingManager extends ProfileContentManager { #loginLockKey = this.profile.constructProfilePropertyKey('loginlock'); async setLoginLock(lock: string) { - await this.kv.getKv().set(this.#deviceClassKey, lock); + await this.kv.getKv().set(this.#loginLockKey, lock); } async getLoginLock(): Promise { return (await this.kv.getKv().get(this.#loginLockKey)).value; diff --git a/src/server/profiles/manager.ts b/src/server/profiles/manager.ts index 0caa3a0..14b37be 100644 --- a/src/server/profiles/manager.ts +++ b/src/server/profiles/manager.ts @@ -4,8 +4,9 @@ import Profile from "./profile.ts"; import { SelfAccount, type RecNetAccount } from "./types/profile.ts"; import Command from "./../commands/command.ts"; import z from "zod"; -import { PlatformMask, ProfileRole, TokenType } from "../platforms/types.ts"; +import { PlatformMask, PlatformType, ProfileRole, TokenType } from "../platforms/types.ts"; import Logging from "@proxnet/undead-logging"; +import { BitFlags } from "../../util/flags.ts"; const profiles: Map = new Map(); @@ -45,7 +46,7 @@ class ProfileManagerBase extends ServerContentBase { else return await this.#getUnusedUsername(); } - async create(platform: number, platformId: string, username?: string, id?: number) { + async create(platform: PlatformType, mask: BitFlags, platformId: string, username?: string, id?: number) { if (typeof id == 'number') { const prof = await this.get(id); if (prof) throw new Error("ID is in use"); @@ -60,14 +61,14 @@ class ProfileManagerBase extends ServerContentBase { accountId: newId, username: newUsername, displayName: newUsername, - platforms: PlatformMask.None, + platforms: mask.getValue(), profileImage: "DefaultProfileImage.png", createdAt: new Date() } await this.kv.getKv().set([ ProfileManagerBase.profilesKey, newId ], newProfile); await this.kv.getKv().set([ ProfileManagerBase.profilesKey, newUsername ], newId); - await this.server.Platforms.addCachedLogin(platform, platformId, newId); + if (platformId !== "") await this.server.Platforms.addCachedLogin(platform, platformId, newId); return this.get(newId); } diff --git a/src/server/rooms/base.ts b/src/server/rooms/base.ts index f4c5b25..ba40873 100644 --- a/src/server/rooms/base.ts +++ b/src/server/rooms/base.ts @@ -39,6 +39,16 @@ export class ServerRoomsBase extends ServerContentBase { if (agrooms.value !== null) this.#agroomIds = agrooms.value; this.#log.i(`${this.#agroomIds.size} AG rooms exist`); + if (this.getAgRoomIds().size === 0) { + try { + const config = await this.tryGetAgRoomRuntimeConfig(); + this.#log.i(`Starting AG room initialization`); + this.initBuiltinRooms(config.Rooms, config.Locations); + } catch (err) { + this.#log.e(`Could not run AG room initialization: ${(err as Error).stack}`); + } + } + const baserooms = await this.kv.getKv().get>([ServerRoomsBase.baseRoomIdsKey]); if (baserooms.value !== null) this.#baseroomIds = baserooms.value; @@ -52,6 +62,8 @@ export class ServerRoomsBase extends ServerContentBase { key: ["initag", "initagrooms", "initagroom", "iag"], zod: z.tuple([]).rest(z.string()), exec: async (...arrayPath: string[]) => { + if (this.#agroomIds.size !== 0) return new Error("AG rooms already initialized"); + const path = arrayPath.join(' '); try { const config = JSON.parse((await Deno.readTextFile(path)).toString()) as AGRoomRuntimeConfig; @@ -105,6 +117,9 @@ export class ServerRoomsBase extends ServerContentBase { return this.#agRoomRuntimeConfig; } + async tryGetAgRoomRuntimeConfig() { + return JSON.parse(await Deno.readTextFile(`${RootPath}/res/rooms.json`)) as AGRoomRuntimeConfig; + } async initBuiltinRooms(rooms: AGRoom[], locations: AGRoomLocation[]) { await Promise.all(rooms.map(async room => { if (room.Accessibility == RoomDataTypes.RoomAccessibility.Private) return; @@ -115,10 +130,7 @@ export class ServerRoomsBase extends ServerContentBase { "ARRoom", "Registration", "DormRoom" - ].includes(room.Name)) { - this.#log.w(`Room '${room.Name}' is not eligible for builtin room generation`); - return; - } + ].includes(room.Name)) return; const roomFactory = await this.write(); if (roomFactory == null) { @@ -185,9 +197,9 @@ export class ServerRoomsBase extends ServerContentBase { if (builtinScene) return builtinScene.SupportsJoinInProgress; else { if (!this.#joinInProgressLookup) throw new Error("JoinInProgress lookup table is not yet initialized"); - const lookup = this.#joinInProgressLookup[subroomFactory.RoomSceneLocationId]; - if (lookup) return lookup; - else return false; + const lookup = this.#joinInProgressLookup[subroomFactory.RoomSceneLocationId]; + if (lookup) return lookup; + else return false; } } diff --git a/src/util/flags.ts b/src/util/flags.ts index fbd3c15..42304be 100644 --- a/src/util/flags.ts +++ b/src/util/flags.ts @@ -10,37 +10,42 @@ export class BitFlags { private value: T; - constructor(initialValue: T | number = 0 as T) { - this.value = initialValue as T; + constructor(...initialValue: (T | number)[]) { + if (Array.isArray(initialValue)) { + // Combine multiple flags into one numeric value + this.value = initialValue.reduce((acc, flag) => acc | flag, 0) as T; + } else { + this.value = initialValue as T; + } } /** Get current numeric value */ - public getValue(): T { + public getValue() { return this.value; } /** Check if a specific flag is set */ - public has(flag: T): boolean { + public has(flag: T) { return (this.value & flag) === flag; } /** Add a flag */ - public add(flag: T): void { + public add(flag: T) { this.value = (this.value | flag) as T; } /** Remove a flag */ - public remove(flag: T): void { + public remove(flag: T) { this.value = (this.value & ~flag) as T; } /** Toggle a flag */ - public toggle(flag: T): void { + public toggle(flag: T) { this.value = (this.value ^ flag) as T; } /** Reset all flags */ - public clear(): void { + public clear() { this.value = 0 as T; } } \ No newline at end of file diff --git a/src/util/validators.ts b/src/util/validators.ts index ab2141a..52d0b28 100644 --- a/src/util/validators.ts +++ b/src/util/validators.ts @@ -4,6 +4,9 @@ import { z } from "zod"; import type { HonoEnv } from "./types.ts"; import { ZodSchema } from "zod/v4"; import { HTTPStatus } from "@oneday/http-status"; +import Logging from "@proxnet/undead-logging"; + +const log = new Logging("Validation"); // thanks claude, this hurt my brain! @@ -17,7 +20,10 @@ export const typedZValidator = < recNetError?: string ) => { return zValidator(target, schema, (result, c) => { + if (!result.success) { + log.w(`Validation failed: ${JSON.stringify(result.error)}`); + if (recNetError) return c.json({ success: false, error: recNetError }, httpOk == true ? HTTPStatus.OK : HTTPStatus.BadRequest); else return c.json({ success: false, error: "Request validation failed" }, httpOk == true ? HTTPStatus.OK : HTTPStatus.BadRequest); }