support for 202507 endpoints (#37)

* [auth][api] accept the 20250424.01 client

* [2025] unstable

* 20250718.0

* correct one this time

* stubs

* more stubs

* more stubs

* [lists] add worker

* [ai] route stubs

* [api] player photo setting

* [econ] add roomEconConfig route

* [infra] update worker generators

* [worker] add cards/moderation/platformnotification workers

* [lists] updates to some endpoints

* [clubs] stub out announcement endpoint, for now

* [econ] stub out season endpoints for now

* [chat] apps/chat stub out party endpoint not sure the shape yet

* [api] stub out statsig and lockeditems

* [doc] new services

* [lists] stub the bulk endpoint

* [datacollection] add placeholder service until we can kill it

* [api] set gifting to lvl5

* update lock

* [cdn] enable cache

* [match] matchmake v2

* [lists] stub some lists

* [ai] stubs

* [rooms] new subroom save endpoint

* [econ] add bulk purchase endpoint

* [discovery] update featured creator to 1 for fun

* [api] add photo settings flag

* [chat] fixup chat permissions (sorta)

* [auth] restrictions endpoint

* [rooms] contributed endpoint

* [api] fix outfit endpoint

* [discovery] attempt to fix store

* [chat] privacy endpoints

* [api] cheered images

* [rooms] add xp endpoint (disbaled)

* [rooms] add xp endpoint (disabled)

* update images-db for cheers

* [rooms] add autocomplete endpoint

* [cdn/img] increase cache ttl for statics

* [api] bulk route for images

* [accounts] add banner image

* [api] add misc missing endpoints

* [discovery] remove AI tab

* [platformnotifications] stub some endpoints

* [lists] add some more lists

* [rooms] additional endpoints

* [chat] stub a few privacy endpoints

* [econ] stub some endpoints

* misc db fixes

* [api] tweak shape for images v6

* [rooms] dont show trending RROs
This commit is contained in:
devin
2026-08-18 23:07:24 -04:00
committed by Devin Zuczek
parent 66c09806f9
commit 178d3b5b0e
162 changed files with 114930 additions and 469 deletions
+141 -7
View File
@@ -104,7 +104,7 @@ export function stringQuery(name: string, description: string): OpenAPIV3_1.Para
}
/** An optional integer query parameter. */
function intQuery(name: string, description: string): OpenAPIV3_1.ParameterObject {
export function intQuery(name: string, description: string): OpenAPIV3_1.ParameterObject {
return { name, in: 'query', required: false, description, schema: { type: 'integer' } }
}
@@ -294,6 +294,13 @@ export const RoomDto = z.object({
PublishedAt: z.string(),
BecameRRStudioRoomAt: z.string().nullable(),
Stats: RoomStatsDto,
BoostCount: z
.int()
.describe('Boosts on the room. Nothing grants boosts here, so always 0 — but present'),
CurrentSnapshotId: z
.int()
.nullable()
.describe('The room’s published snapshot. Nothing takes snapshots here, so always null'),
RankingContext: z.unknown().nullable(),
IsDorm: z.boolean().describe('Auto-provisioned personal room; excluded from every feed'),
IsPlacePlay: z.boolean(),
@@ -333,6 +340,35 @@ export const PagedRooms = z.object({
TotalResults: z.int().describe('The full match count, not the page size'),
})
/**
* `GET /rooms/autocomplete_search` — the search box's suggestions: a bare array of plain
* STRINGS, not rooms and not an envelope. Each is a query the player can submit as-is; a
* tag suggestion carries its `#` so submitting it searches by tag.
*/
export const SearchSuggestions = z
.array(z.string())
.describe('Suggested search terms, best match first; empty when nothing matches')
/**
* `GET /rooms/{roomId}/experience` — whether players earn XP in a room and how much of it
* counts in a day. A bare two-key object, no envelope.
*
* Nothing here meters per-room XP: progression is the `api` worker's, and it applies no
* room-scoped daily cap. So this is the config the client reads, not a limit this server
* enforces — the same answer for every room.
*/
export const RoomExperience = z.object({
Enabled: z.boolean().describe('Whether XP is earned in the room at all. Always false here'),
DailyLimit: z.int().describe('XP from this room that counts toward a player’s day'),
})
/**
* `GET /dormroom/me` — the dorm's `RoomId` as a BARE JSON number, not a room and not an
* envelope around one. The caller follows it with `GET /rooms/{roomId}` when it wants the
* room itself, so sending the whole DTO here was a payload nobody read.
*/
export const DormRoomId = z.int().describe('The caller’s dorm RoomId')
/**
* A room lookup result: the room, or `{}` when nothing matched. The by-id/by-name
* lookups answer an empty object rather than a 404 — the client reads that as "no room".
@@ -344,6 +380,25 @@ export const MissingLookupParam = z
.string()
.describe("`\"Either 'id' or 'name' query parameter is required\"`")
/**
* `GET /Room_server/rooms/{roomId}/bans/{playerId}/isBanned` — the ban check's envelope.
*
* NOT the `{ success, error, value }` the room mutations answer with: this one carries an
* `error_id` as well, and its `error` is NULL rather than the empty string those use. Same
* distinction the client's other envelopes draw, so don't unify them.
*/
export const IsBannedEnvelope = z.object({
success: z.literal(true).describe('The check ran; whether the player is banned is `value`'),
error: z.string().nullable().describe('Null — the check itself does not fail'),
error_id: z.string().nullable().describe('Null. Present as a key, unlike the room envelope'),
value: z.boolean().describe('Whether that player is banned from that room'),
})
/** The bare JSON string the bulk lookups answer when the id list is over the cap. */
export const TooManyLookupIds = z
.string()
.describe('`"At most 100 room ids may be looked up at once"`')
// ---- Interaction -----------------------------------------------------------
/**
@@ -505,6 +560,19 @@ export const CloningRequest = z.object({
cloningAllowed: z.string().describe('`True` / `False`'),
})
/**
* `POST /rooms/bulk` form body — the room ids to look up, as a REPEATED `id` field
* (`id=888&id=532&…`), one value per id. This is how the client asks for a whole room list
* at once (70-odd ids in the wild), which is more than belongs in a query string.
*/
export const BulkRoomsRequest = z.object({
id: z.string().describe('Repeated once per room id; each value may also be comma-separated'),
excludePrivateRooms: z
.string()
.optional()
.describe('`True` drops rooms that are not publicly visible. Default `False`'),
})
/**
* `PUT /rooms/{roomId}/restrictions` — the room's platform/movement support flags. Only
* the fields actually posted are changed, and the names are matched case-insensitively.
@@ -626,14 +694,51 @@ export const SaveSubRoomDataRequest = z.object({
})
/**
* `GET /rooms/{roomId}/subrooms/{subRoomId}/saves` — the room-history page. We keep no
* save history (a save overwrites the subroom's blob inline), so it's always empty.
* `GET /rooms/{roomId}/subrooms/{subRoomId}/saves` — the room-history page. Every save
* appends a `subroom_save` row rather than overwriting, so this is the subroom's full
* history, newest first, paged by `skip`/`take`.
*/
export const SubRoomSavesPage = z.object({
Results: z
.array(SubRoomDataSaveDto)
.describe('At most one — the current save; we keep no history'),
TotalResults: z.int(),
Results: z.array(SubRoomDataSaveDto).describe('The page of saves, newest first'),
TotalResults: z.int().describe('The whole history’s size, not the page’s'),
TotalCount: z.int().describe('Same value as `TotalResults` — the two references disagree'),
})
/**
* One save as `GET …/subrooms/{subRoomId}/saves/no_unity_assets` lists it: the same
* PascalCase row as {@link SubRoomDataSaveDto} with the Unity-asset payloads left out —
* no `UnitySubAssets`, no `ReferencedUnityAssets`, no `Tags` — keeping only the asset
* IDENTIFIERS (`UnityAssetId`, `ReferencedUnityAssetIds`).
*
* The one field that differs rather than disappearing is `UnityAssetId`: always present
* here, null when the save carried none, where the full row emits it only when it did.
*/
export const SubRoomDataSaveNoUnityAssetsDto = z.object({
SubRoomDataSaveId: z.int(),
SubRoomId: z.int(),
UnityAssetId: z.string().nullable().describe('Null unless the save carried one'),
ReferencedUnityAssetIds: z.array(z.string()).describe('Always empty — we record none'),
DataBlob: z.string().describe('The scene-data key the client downloads from the CDN'),
DataBlobHash: z.string().nullable(),
PersistenceVersion: z.int(),
OMVersion: z.int(),
SavedByAccountId: z.int().nullable(),
SavedOnPlatform: z.int().describe('0 — the save request carries no platform'),
SavedOnDeviceClass: z.int().describe('0 — the save request carries no device class'),
Description: z.string().describe('The save comment; empty string when none'),
ModerationState: z.int(),
CreatedAt: z.string(),
UgcSubVersion: z.int(),
})
/**
* `GET /rooms/{roomId}/subrooms/{subRoomId}/saves/no_unity_assets` — the same history page
* as {@link SubRoomSavesPage}, carrying the lighter rows. A BARE paged wrapper: no
* `{ success, error, value }` envelope around it, unlike the room mutations.
*/
export const SubRoomSavesNoUnityAssetsPage = z.object({
Results: z.array(SubRoomDataSaveNoUnityAssetsDto).describe('The page of saves, newest first'),
TotalResults: z.int().describe('The whole history’s size, not the page’s'),
TotalCount: z.int().describe('Same value as `TotalResults` — the two references disagree'),
})
@@ -675,3 +780,32 @@ export const PhotonAccessTokenDto = z.object({
export const PlayerDataDto = z.object({
Data: z.string().describe('Always empty — no per-room player data is stored'),
})
/**
* `GET /rooms/{roomId}/experience/player` — the caller's per-room experience/progression
* entries. Stubbed empty; the element shape is unknown until something stores one.
*/
export const RoomExperiencePlayer = z
.array(z.unknown())
.describe('Always empty — no per-room experience is tracked')
/**
* `GET /publishState/configs` — the limits the client enforces on republishing a room:
* how many updates are allowed in the rolling window, and the cooldown/expiry around
* them. Served as fixed values from the reference server.
*
* The envelope is NOT the `{ success, error, value }` one the room mutations use: `error`
* is null rather than `""`, and there's an extra `error_id`. Kept as-is — the client
* reads both keys.
*/
export const PublishStateConfigsEnvelope = z.object({
value: z.object({
UpdateMaxCount: z.int().describe('Updates allowed per rolling window'),
UpdateRollingWindowInDays: z.int().describe('Length of that window, in days'),
UpdateExpirationInDays: z.int().describe('Days before an update expires'),
UpdateCooldownInDays: z.int().describe('Days between updates'),
}),
success: z.literal(true),
error_id: z.null(),
error: z.null(),
})