feat: return workshop metadata with mod details
Include the Workshop title and preview image in the items response so the mod header survives a page refresh, not just navigation from search. Resolve both from the same Steam API call.
This commit is contained in:
@@ -87,6 +87,14 @@ export interface TTSMod {
|
||||
ObjectStates: TTSObject[];
|
||||
}
|
||||
|
||||
/** A parsed save plus its Workshop metadata, returned by `GET /items/:id`. */
|
||||
export interface ModDetails {
|
||||
mod: TTSMod;
|
||||
/** Present when the metadata could be resolved via the Steam API. */
|
||||
title?: string;
|
||||
previewImageUrl?: string;
|
||||
}
|
||||
|
||||
/** Metadata for a Workshop item, from the Steam Web API. */
|
||||
export interface WorkshopItem {
|
||||
id: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { deserialize } from 'bson';
|
||||
import type { TTSMod } from '@tts/shared';
|
||||
import type { ModDetails, TTSMod } from '@tts/shared';
|
||||
import { ItemNotFoundError, NoFileError, SteamApiError } from './errors.js';
|
||||
|
||||
const STEAM_API_URL =
|
||||
@@ -36,6 +36,30 @@ export async function fetchMod(id: string, apiKey: string): Promise<TTSMod> {
|
||||
return fetchModFromUrl(fileUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a save plus its Workshop metadata (title, preview image) in one call.
|
||||
* The metadata comes from the same Steam API request that resolves the save
|
||||
* URL, so it costs no extra round-trip.
|
||||
*
|
||||
* @param id Workshop item ID (digits only).
|
||||
* @param apiKey Steam Web API key.
|
||||
*/
|
||||
export async function fetchModDetails(
|
||||
id: string,
|
||||
apiKey: string,
|
||||
): Promise<ModDetails> {
|
||||
const details = await getPublishedFileDetails(id, apiKey);
|
||||
if (!details.file_url) {
|
||||
throw new NoFileError(id);
|
||||
}
|
||||
const mod = await fetchModFromUrl(details.file_url);
|
||||
return {
|
||||
mod,
|
||||
title: details.title,
|
||||
previewImageUrl: details.preview_url,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a TTS save from a direct URL and BSON-deserialize it.
|
||||
*
|
||||
@@ -94,6 +118,18 @@ async function downloadSave(fileUrl: string): Promise<ArrayBuffer> {
|
||||
|
||||
/** Resolve the `file_url` for a Workshop item via the Steam API. */
|
||||
async function getFileUrl(id: string, apiKey: string): Promise<string> {
|
||||
const details = await getPublishedFileDetails(id, apiKey);
|
||||
if (!details.file_url) {
|
||||
throw new NoFileError(id);
|
||||
}
|
||||
return details.file_url;
|
||||
}
|
||||
|
||||
/** Fetch the Steam published-file details for a Workshop item. */
|
||||
async function getPublishedFileDetails(
|
||||
id: string,
|
||||
apiKey: string,
|
||||
): Promise<SteamPublishedFileDetails> {
|
||||
const params = new URLSearchParams();
|
||||
params.append('key', apiKey);
|
||||
params.append('itemcount', '1');
|
||||
@@ -112,10 +148,7 @@ async function getFileUrl(id: string, apiKey: string): Promise<string> {
|
||||
if (!details) {
|
||||
throw new ItemNotFoundError(id);
|
||||
}
|
||||
if (!details.file_url) {
|
||||
throw new NoFileError(id);
|
||||
}
|
||||
return details.file_url;
|
||||
return details;
|
||||
}
|
||||
|
||||
export * from './errors.js';
|
||||
Reference in New Issue
Block a user