feat: allow loading mods without a Steam API key

Add fetchModFromUrl/fetchModFileFromUrl and accept a fileUrl query param on /items routes so the frontend can download saves directly from a search result's file_url, with no key required.
This commit is contained in:
2026-08-08 11:32:57 +08:00
parent 4ffe858c36
commit f7a6eb6ee1
9 changed files with 170 additions and 48 deletions
+26 -13
View File
@@ -33,16 +33,16 @@ interface SteamResponse {
*/
export async function fetchMod(id: string, apiKey: string): Promise<TTSMod> {
const fileUrl = await getFileUrl(id, apiKey);
return fetchModFromUrl(fileUrl);
}
const saveRes = await fetch(fileUrl);
if (!saveRes.ok) {
throw new SteamApiError(
`Failed to download save file (${saveRes.status})`,
502,
);
}
const buffer = await saveRes.arrayBuffer();
/**
* Download a TTS save from a direct URL and BSON-deserialize it.
*
* @param fileUrl Direct URL to the save file (e.g. from a search result).
*/
export async function fetchModFromUrl(fileUrl: string): Promise<TTSMod> {
const buffer = await downloadSave(fileUrl);
return deserialize(new Uint8Array(buffer)) as TTSMod;
}
@@ -70,7 +70,23 @@ export async function fetchModFile(
apiKey: string,
): Promise<{ data: ArrayBuffer; filename: string }> {
const fileUrl = await getFileUrl(id, apiKey);
return fetchModFileFromUrl(fileUrl);
}
/**
* Fetch the raw save file bytes from a direct URL.
* Returns the bytes plus a derived filename.
*/
export async function fetchModFileFromUrl(
fileUrl: string,
): Promise<{ data: ArrayBuffer; filename: string }> {
const data = await downloadSave(fileUrl);
const filename = getFileName(fileUrl, null);
return { data, filename };
}
/** Download a save file's bytes from a URL, throwing on failure. */
async function downloadSave(fileUrl: string): Promise<ArrayBuffer> {
const res = await fetch(fileUrl);
if (!res.ok) {
throw new SteamApiError(
@@ -78,10 +94,7 @@ export async function fetchModFile(
502,
);
}
const data = await res.arrayBuffer();
const filename = getFileName(fileUrl, res.headers.get('content-disposition'));
return { data, filename };
return res.arrayBuffer();
}
/** Resolve the `file_url` for a Workshop item via the Steam API. */