duhhhhhhhh

This commit is contained in:
2025-09-11 13:47:30 -04:00
parent eef3667618
commit 317da3aaf7
53 changed files with 1395 additions and 212 deletions

View File

@@ -1,10 +1,11 @@
import { createHonoRoute } from "../../../util/import.ts";
import { authenticate, galvanicError, GalvanicErrors, RateLimiter, recNetError } from "../../../util/api.ts";
import { authenticate, galvanicError, GalvanicErrors, RateLimiter, recNetError, statusResponse } from "../../../util/api.ts";
import Server from "../../../server/server.ts";
import z from "zod";
import { transformStringToEnum, typedZValidator } from "../../../util/validators.ts";
import { transformCheckEnum, typedZValidator } from "../../../util/validators.ts";
import { PlatformType } from "../../../server/platforms/types.ts";
import Steam from "../../../util/steam/steam.ts";
import { HTTPStatus } from "@oneday/http-status";
export const route = createHonoRoute('/account');
@@ -28,7 +29,7 @@ route.app.get('/bulk', typedZValidator('query', bulkAccountQuerySchema), async c
const postCreateRateLimiter = new RateLimiter(60, 3);
const createAccountBodySchema = z.object({
platform: z.string().transform(transformStringToEnum<PlatformType>(PlatformType)),
platform: z.coerce.number().transform(transformCheckEnum<PlatformType>(PlatformType)),
platformId: z.string().min(14).max(20),
deviceId: z.string().min(32).max(64)
});
@@ -71,9 +72,16 @@ route.app.post('/create', postCreateRateLimiter.middle(), typedZValidator('form'
});
route.app.use(authenticate);
route.app.get('/me', c => {
route.app.get('/me', authenticate, c => {
const profile = c.get('profile');
return c.json(profile.selfExport());
});
const getAccountByIdParamSchema = z.object({
id: z.coerce.number().max(Math.pow(2, 31))
});
route.app.get('/:id', typedZValidator('param', getAccountByIdParamSchema), async c => {
const prof = await Server.Profiles.get(c.req.valid('param').id);
if (prof) return c.json(prof.export());
else return statusResponse(c, HTTPStatus.NotFound);
});

View File

@@ -0,0 +1,8 @@
import Server from "../../../server/server.ts";
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute("/announcement");
route.app.get('/v1/get', c => {
return c.json(Server.getAnnouncements());
});

View File

@@ -24,4 +24,12 @@ route.app.post('/v2/set', typedZValidator('json', profileAvatarSchema), async c
const outfit = c.req.valid('json');
await c.get('profile').Avatar.setAvatar(outfit);
return c.status(200);
});
route.app.get('/v3/saved', c => {
return c.json([]); // stub
});
route.app.get('/v2/gifts', c => {
return c.json([]); // stub
});

View File

@@ -0,0 +1,19 @@
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute('/challenge');
route.app.get('/v2/getCurrent', c => {
return c.json({
ChallengeMapId: 0,
CompletedRequired: false,
StartAt: new Date(new Date().getTime() - 604_800_000).toISOString(),
EndAt: new Date(new Date().getTime() + 999_999_999_999).toISOString(),
ServerTime: new Date().toISOString(),
Challenges: [],
Gift: {
GiftDropId: 0,
Xp: 0,
Level: 0
}
}) // stub
});

View File

@@ -0,0 +1,171 @@
import { ObjectiveType } from "../../../server/objectives/base.ts";
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute('/checklist');
route.app.get('/v1/current', c => {
return c.json([
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
},
{
Order: 1,
Objective: ObjectiveType.CompleteAnyDaily,
Count: 1,
CreditAmount: -1
}
]); // stub
});

View File

@@ -0,0 +1,23 @@
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute("/communityboard");
route.app.get('/v1/current', c => {
return c.json({
FeaturedPlayer: {
Id: 1,
TitleOverride: "",
UrlOverride: ""
},
FeaturedRoomGroup: {
Name: "",
FeaturedRooms: []
},
CurrentAnnouncement: {
Message: "Galvanic Corrosion",
MoreInfoUrl: ""
},
InstagramImages: [],
Videos: []
}); // stub
});

View File

@@ -0,0 +1,8 @@
import Server from "../../../server/server.ts";
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute("/consumables");
route.app.get('/v1/getUnlocked', c => {
return c.json(Server.Consumables.getAllDev());
});

View File

@@ -0,0 +1,7 @@
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute("/equipment");
route.app.get('/v2/getUnlocked', c => {
return c.json([]); // stub
});

View File

@@ -0,0 +1,7 @@
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute('/images');
route.app.get('/v2/named', c => {
return c.json([]); // stub
});

View File

@@ -0,0 +1,10 @@
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute('/objectives');
route.app.get('/v1/myprogress', c => {
return c.json({
Objectives: [],
ObjectiveGroups: []
}); // stub
});

View File

@@ -0,0 +1,10 @@
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute('/playerevents');
route.app.get('/v1/all', c => {
return c.json({
Created: [],
Responses: []
}) // stub
});

View File

@@ -14,14 +14,14 @@ const getProgParamSchema = z.object({
id: z.coerce.number()
});
route.app.get('/v1/progression/:id', authenticate, typedZValidator('param', getProgParamSchema), async c => {
return c.json(await c.get('profile').Reputation.export());
return c.json(await c.get('profile').Progression.get());
});
const getProgBulkBodySchema = z.object({
Ids: z.union([z.array(z.coerce.number()), z.coerce.number()])
});
route.app.post('/v1/progression/bulk', authenticate, typedZValidator('form', getProgBulkBodySchema), async c => {
const ids = c.req.valid('form').Ids;
route.app.post('/v1/progression/bulk', authenticate, typedZValidator('json', getProgBulkBodySchema), async c => {
const ids = c.req.valid('json').Ids;
if (typeof ids == 'object') {
const profs = await Server.Profiles.getMany(...ids);

View File

@@ -0,0 +1,7 @@
import { createHonoRoute } from "../../../util/import.ts";
export const route = createHonoRoute("/playersubscriptions");
route.app.get('/v1/my', c => {
return c.json([]); // stub
});

View File

@@ -8,7 +8,6 @@ route.app.get('/v2/get', c => {
return c.json([]);
});
// deno-lint-ignore require-await
route.app.post('/v1/bulkignoreplatformusers', async c => {
route.app.post('/v1/bulkignoreplatformusers', c => {
return statusResponse(c, HTTPStatus.OK);
});

View File

@@ -2,6 +2,7 @@ import z from "zod";
import { createHonoRoute } from "../../../util/import.ts";
import { typedZValidator } from "../../../util/validators.ts";
import Server from "../../../server/server.ts";
import { authenticate } from "../../../util/api.ts";
export const route = createHonoRoute("/rooms");
@@ -23,4 +24,13 @@ route.app.get('/v2/name/:name', typedZValidator('param', getRoomByNameParamSchem
const room = await Server.Rooms.get(id);
if (room == null) return await nxt();
else return c.json((await room.export()).Room);
});
route.app.get('/v2/myrooms', authenticate, async c => {
const myrooms = c.get('profile').Rooms.getRooms().values().toArray();
const factories = await Server.Rooms.getMany(...myrooms);
const exs = await Promise.all(factories.map(factory => factory.export()));
const rooms = exs.map(ex => ex.Room);
return c.json(rooms);
});

View File

@@ -1,6 +1,6 @@
import z from "zod";
import { createHonoRoute } from "../../../util/import.ts";
import { transformStringToEnum, typedZValidator } from "../../../util/validators.ts";
import { transformCheckEnum, typedZValidator } from "../../../util/validators.ts";
import { PlatformType } from "../../../server/platforms/types.ts";
import Server from "../../../server/server.ts";
import { authenticate } from "../../../util/api.ts";
@@ -11,7 +11,7 @@ const log = new Logging("CachedLoginDebug");
export const route = createHonoRoute("/cachedlogin");
const cachedLoginFetchParamSchema = z.object({
platformType: z.string().transform(transformStringToEnum<PlatformType>(PlatformType)),
platformType: z.coerce.number().transform(transformCheckEnum<PlatformType>(PlatformType)),
platformId: z.string().min(4)
});

View File

@@ -1,6 +1,6 @@
import { createHonoRoute } from "../../../util/import.ts";
import z from "zod";
import { transformStringToEnum, typedZValidator } from "../../../util/validators.ts";
import { transformCheckEnum, typedZValidator } from "../../../util/validators.ts";
import { DeviceClass, PlatformType, TokenFormat, TokenType } from "../../../server/platforms/types.ts";
import { steamAuthTicketSchema } from "../../../server/platforms/base.ts";
import { gameVerString } from "../../api/routes/versioncheck.ts";
@@ -16,13 +16,13 @@ export const route = createHonoRoute("/connect");
const authBodyBaseSchema = z.object({
client_id: z.literal("recroom"),
platform: z.string().transform(Number).transform((arg, ctx) => { // we only support steam right now
platform: z.coerce.number().transform((arg, ctx) => { // we only support steam right now
if (arg !== PlatformType.Steam) ctx.addIssue("platform was not Steam");
else return PlatformType.Steam;
}),
platform_id: z.string().min(4),
device_id: z.string().min(4),
device_class: z.string().transform(transformStringToEnum<DeviceClass>(DeviceClass)),
device_class: z.string().transform(transformCheckEnum<DeviceClass>(DeviceClass)),
time: z.coerce.date(),
ver: z.literal(gameVerString),
asid: z.coerce.number(),
@@ -39,7 +39,7 @@ const authBodyBaseSchema = z.object({
const cachedLoginGrantSchema = authBodyBaseSchema.extend({
grant_type: z.literal('cached_login'),
account_id: z.string().transform(Number),
account_id: z.coerce.number(),
});
const refreshTokenGrantSchema = authBodyBaseSchema.extend({
grant_type: z.literal('refresh_token'),
@@ -97,24 +97,25 @@ route.app.post('/token', typedZValidator('form', tokenGrantSchema), async c => {
const profile = await Server.Profiles.get(token.sub);
if (!profile) return error(TokenRequestError.AccessDenied);
const accessToken = await Server.Platforms.getToken(profile.getId(), TokenType.Access);
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: form.refresh_token,
refresh_token: refreshToken,
});
} catch (err) {
log.w(`Authentication error (token req): ${(err as Error).stack}`);
return error(TokenRequestError.InvalidClient);
}
}
if (logins.find(login => login.accountId === form.account_id)) {
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.getId(), TokenType.Access);
const refreshToken = await Server.Platforms.getToken(profile.getId(), TokenType.Refresh);
const accessToken = await Server.Platforms.getToken(profile, TokenType.Access);
const refreshToken = await Server.Platforms.getToken(profile, TokenType.Refresh);
return c.json({
access_token: accessToken,

View File

@@ -1,50 +1,7 @@
import { createHonoRoute, routeImporter } from "../../util/import.ts";
import { Context, Next } from "@hono/hono";
import z from "zod";
import { HonoEnv } from "../../util/types.ts";
import { statusResponse } from "../../util/api.ts";
import { HTTPStatus } from "@oneday/http-status";
import Logging from "@proxnet/undead-logging";
const log = new Logging("MatchRoute");
export const route = createHonoRoute('/match');
const loginLockBodySchema = z.object({
LoginLock: z.uuidv4()
});
export const loginLockMiddleware = async (c: Context<HonoEnv>, nxt: Next) => {
function unauthorized() {
return statusResponse(c, HTTPStatus.Unauthorized);
}
if (c.req.header("Content-Type") !== "application/x-www-form-urlencoded") return unauthorized();
try {
const form = await c.req.formData();
const body = await loginLockBodySchema.safeParseAsync(Object.fromEntries(form.entries()));
if (body.success) {
if (typeof c.get('profile') == 'undefined') {
log.w(`Profile was not set, cannot validate LoginLock. Was the request authorized?`);
return statusResponse(c, HTTPStatus.InternalServerError);
}
const profile = c.get('profile');
const loginLock = await profile.Matchmaking.getLoginLock();
if (!loginLock) await profile.Matchmaking.setLoginLock(body.data.LoginLock);
else if (body.data.LoginLock !== loginLock) {
log.w(`LoginLock did not match. The token for this profile could be compromised or the client is an unknown state.`);
return unauthorized();
}
return await nxt();
} else return unauthorized();
} catch {
return unauthorized();
}
}
await routeImporter(route.app, 'src/routes/match/', [
'routes'
]);

View File

@@ -1,13 +1,76 @@
import { HTTPStatus } from "@oneday/http-status";
import { statusResponse } from "../../../util/api.ts";
import { authenticate, loginLockMiddleware, statusResponse } from "../../../util/api.ts";
import { createHonoRoute } from "../../../util/import.ts";
import Server from "../../../server/server.ts";
import z from "zod";
import { typedZValidator } from "../../../util/validators.ts";
import { type MatchmakingResponse } from "../../../server/matchmaking/base.ts";
import { roomNameSchema } from "../../../server/rooms/base.ts";
export const route = createHonoRoute("/goto");
route.app.post('/room/:roomName', c => {
return statusResponse(c, HTTPStatus.NotImplemented);
const gotoRoomBodySchema = z.object({
CreatePrivateInstance: z.boolean().optional(),
ExpectedPlayerIds: z.array(z.int()).optional(),
BypassMovementModeRestriction: z.boolean().optional()
});
route.app.post('/room/:roomName/:subRoomName', c => {
return statusResponse(c, HTTPStatus.NotImplemented);
});
const gotoRoomParamSchema = z.object({
roomName: roomNameSchema
});
route.app.post('/room/:roomName',
authenticate,
loginLockMiddleware,
typedZValidator('json', gotoRoomBodySchema),
typedZValidator('param', gotoRoomParamSchema),
async c => {
const body = c.req.valid("json");
const res = await Server.Matchmaking.matchmake({
roomName: c.req.param('roomName'),
private: body.CreatePrivateInstance,
profile: c.get('profile')
});
if (!res) return statusResponse(c, HTTPStatus.InternalServerError, "Matchmaking failed");
const m: MatchmakingResponse = {
roomInstance: res.roomInstance ? res.roomInstance.export() : undefined,
errorCode: res.errorCode
}
return c.json(m);
}
);
const gotoSubroomParamSchema = gotoRoomParamSchema.extend({
subRoomName: roomNameSchema
});
route.app.post('/room/:roomName/:subRoomName',
authenticate,
loginLockMiddleware,
typedZValidator('json', gotoRoomBodySchema),
typedZValidator('param', gotoSubroomParamSchema),
async c => {
const body = c.req.valid("json");
const res = await Server.Matchmaking.matchmake({
roomName: c.req.param('roomName'),
subRoomName: c.req.param('subRoomName'),
private: body.CreatePrivateInstance,
profile: c.get('profile')
});
if (!res) return statusResponse(c, HTTPStatus.InternalServerError, "Matchmaking failed");
const m: MatchmakingResponse = {
roomInstance: res.roomInstance ? res.roomInstance.export() : undefined,
errorCode: res.errorCode
}
return c.json(m);
}
);

View File

@@ -1,20 +1,70 @@
import { authenticate, statusResponse } from "../../../util/api.ts";
import z from "zod";
import Server from "../../../server/server.ts";
import { authenticate, loginLockMiddleware, statusResponse } from "../../../util/api.ts";
import { createHonoRoute } from "../../../util/import.ts";
import { loginLockMiddleware } from "../root.ts";
import { HTTPStatus } from "@oneday/http-status";
import { transformCheckEnum, typedZValidator } from "../../../util/validators.ts";
import { PlayerStatusVisibility, VRMovementMode } from "../../../server/presence/base.ts";
export const route = createHonoRoute("/player");
route.app.use(authenticate);
route.app.post('/login', authenticate, loginLockMiddleware, async c => {
const playerIdsQuerySchema = z.object({
id: z.union([z.coerce.number(), z.array(z.coerce.number())])
});
route.app.get('/', typedZValidator('query', playerIdsQuerySchema), async c => {
const id = c.req.valid('query').id;
if (typeof id == 'object') {
const profs = await Server.Profiles.getMany(...id);
return c.json(profs.map(prof => Server.Presence.getPresence(prof).export()));
} else {
const prof = await Server.Profiles.get(id);
if (!prof) return c.json([]);
return c.json([Server.Presence.getPresence(prof).export()]);
}
});
route.app.post('/player/vrmovementmode', authenticate, loginLockMiddleware, async c => {
return statusResponse(c, HTTPStatus.OK); // stub
route.app.post('/login', authenticate, loginLockMiddleware, c => {
const pres = Server.Presence.getPresence(c.get('profile'));
pres.updateLastSeen();
return statusResponse(c, HTTPStatus.OK);
});
route.app.post('/logout', authenticate, loginLockMiddleware, c => {
const pres = Server.Presence.getPresence(c.get('profile'));
pres.updateLastSeen();
pres.setStatusVisibility(PlayerStatusVisibility.Offline);
c.get('profile').updateInstance(null);
Server.Instances.clearEmptyInstances();
return statusResponse(c, HTTPStatus.OK);
});
route.app.post('/player/statusvisibility', authenticate, loginLockMiddleware, async c => {
return statusResponse(c, HTTPStatus.OK); // stub
const vrMovementModeBodySchema = z.object({
vrMovementMode: z.coerce.number().transform(transformCheckEnum<VRMovementMode>(VRMovementMode))
});
route.app.put('/vrmovementmode', authenticate, typedZValidator('form', vrMovementModeBodySchema), c => {
const pres = Server.Presence.getPresence(c.get('profile'));
pres.updateLastSeen();
return statusResponse(c, HTTPStatus.OK);
});
const statusVisibilityBodySchema = z.object({
statusVisibility: z.coerce.number().transform(transformCheckEnum<PlayerStatusVisibility>(PlayerStatusVisibility))
});
route.app.put('/statusvisibility', authenticate, typedZValidator('form', statusVisibilityBodySchema), c => {
const pres = Server.Presence.getPresence(c.get('profile'));
pres.updateLastSeen();
return statusResponse(c, HTTPStatus.OK);
});
route.app.post('/heartbeat', authenticate, loginLockMiddleware, c => {
const pres = Server.Presence.getPresence(c.get('profile'));
return c.json(pres.export());
});