All checks were successful
Galvanic Corrosion Cross-Compile / build (push) Successful in 49s
* Added Storage and room saving (will be moved to events later)
* Moved `UnifiedProfile` to new `Server` object, along with `CDN`
- Will move `Rooms` and others to this later
159 lines
5.0 KiB
TypeScript
159 lines
5.0 KiB
TypeScript
/* Galvanic Corrosion - Rec Room custom server for communities.
|
|
<https://gitea.proxnet.dev/zombieb/galvanic-corrosion>
|
|
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 <https://www.gnu.org/licenses/>. */
|
|
|
|
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<NoBody, PresenceExport[], NoBody, { id: string[] | string }>, 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<NoBody, NoBody, BaseLoginLock>, 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<NoBody, NoBody, StatusVisibilityBody>, 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<NoBody, NoBody, VRMovementModeBody>, 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);
|
|
}
|
|
|
|
); |