/* Galvanic Corrosion - Rec Room custom server for communities. Copyright (C) 2025 @zombieb (Discord / proxnet Gitea) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ import { z } from "zod"; import { APIUtils, NoBody } from "../../apiutils.ts"; import express from "express"; import Matchmaking from "../../data/live/base.ts"; import Presence, { PresenceExport } from "../../data/live/presence.ts"; import { AuthType } from "../../data/users.ts"; import { PlayerStatusVisibility, VRMovementMode } from "../../data/live/types.ts"; import { SettingKey } from "../../data/content/settings.ts"; import Server from "../../data/server.ts"; export const route = APIUtils.createRouter('/player'); interface BaseLoginLock { LoginLock: string } const LoginSchema = z.object({ LoginLock: z.string().uuid("LoginLock must be a UUIDv4") }); route.router.get('/', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), APIUtils.validateQuery(z.object({ id: z.union([z.string(), z.array(z.string())]) })), async (rq: express.Request, rs) => { let ids: number[] = []; if (typeof rq.query.id == 'object') ids = rq.query.id.map(val => parseInt(val)); else ids.push(parseInt(rq.query.id)); ids = ids.filter(val => !isNaN(val)); const presExport: PresenceExport[] = []; for (const id of ids) { const profile = Server.UnifiedProfile.get(id); if (!profile) continue; const pres = await Presence.get(profile); presExport.push(await pres.export()); } rs.json(presExport); } ); route.router.post('/login', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), express.urlencoded({extended: true}), APIUtils.validateRequestBody(LoginSchema), (rq: express.Request, rs: express.Response) => { Matchmaking.createLoginLock(rs.locals.profile, rq.body.LoginLock); Presence.create(rs.locals.profile); rs.sendStatus(200); }, ); route.router.post('/logout', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), express.urlencoded({extended: true}), APIUtils.validateRequestBody(LoginSchema), (_rq, rs) => { rs.locals.profile.getInstance()?.removePlayer(rs.locals.profile); rs.sendStatus(200); } ) route.router.post('/heartbeat', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), express.urlencoded({extended: true}), APIUtils.validateRequestBody(LoginSchema), APIUtils.LoginLock, async (_rq, rs) => { const pres = await Presence.get(rs.locals.profile); rs.json(await pres.export()); } ); interface StatusVisibilityBody { statusVisibility: PlayerStatusVisibility } const StatusVisibilitySchema = z.object({ statusVisibility: z.enum(Object.values(PlayerStatusVisibility).map(String) as [string, ...string[]]) }); route.router.put('/statusvisibility', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), express.urlencoded({ extended: true }), APIUtils.validateRequestBody(StatusVisibilitySchema), async (rq: express.Request, rs: express.Response) => { rs.locals.profile.Settings.setSetting(SettingKey.PlayerStatusVisibility, rq.body.statusVisibility.toString()); (await Presence.get(rs.locals.profile)).updateStatusVisibility(); rs.sendStatus(200); }, ); interface VRMovementModeBody { vrMovementMode: VRMovementMode } const VRMovementModeSchema = z.object({ vrMovementMode: z.enum(Object.values(VRMovementMode).map(String) as [string, ...string[]]) }); route.router.put('/vrmovementmode', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), express.urlencoded({ extended: true }), APIUtils.validateRequestBody(VRMovementModeSchema), async (rq: express.Request, rs: express.Response) => { rs.locals.profile.Settings.setSetting(SettingKey.VRMovementMode, rq.body.vrMovementMode.toString()); (await Presence.get(rs.locals.profile)).updateVRMovementMode(); rs.sendStatus(200); }, ); route.router.put('/photonregionpings', APIUtils.Authentication, APIUtils.AuthenticationType(AuthType.Game), (_rq, rs) => { rs.sendStatus(200); } );