image library changes, bit flags changes

This commit is contained in:
2025-09-16 18:01:55 -04:00
parent 5934f1a91c
commit c4f32b1940
16 changed files with 119 additions and 284 deletions

View File

@@ -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"

235
deno.lock generated
View File

@@ -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"
]
}

View File

@@ -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>(PlatformMask.All), "", "Coach", 1);
});
});

View File

@@ -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>(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>(PlatformMask.Steam), form.platformId);
if (!profile) return recNetResultResponse(c, HTTPStatus.OK, false, "Account could not be created");
return c.json({

View File

@@ -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 });
});

View File

@@ -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') {

View File

@@ -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));
});

View File

@@ -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");
});

View File

@@ -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<ArrayBufferLike>): Promise<Uint8Array<ArrayBufferLike> | 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<typeof imgQuerySchema>;
@@ -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<ArrayBufferLike>[] = (await Promise.all<Uint8Array<ArrayBufferLike> | 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]);

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<string | null> {
return (await this.kv.getKv().get<string>(this.#loginLockKey)).value;

View File

@@ -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<number, Profile> = 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<PlatformMask>, 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);
}

View File

@@ -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<Set<number>>([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;
}
}

View File

@@ -10,37 +10,42 @@
export class BitFlags<T extends number> {
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;
}
}

View File

@@ -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);
}