mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
Soportar nombres de juegos personalizados desde games.json
This commit is contained in:
@@ -48,6 +48,7 @@ La descarga de assets usa **únicamente HTTP**. Debes configurar un servidor pro
|
||||
|
||||
```text
|
||||
games/
|
||||
games.json (opcional, para nombres visibles personalizados)
|
||||
street-fighter-6/
|
||||
street-fighter-6.png
|
||||
manifest.json
|
||||
@@ -59,6 +60,26 @@ games/
|
||||
...
|
||||
```
|
||||
|
||||
`games/games.json` es opcional y permite mapear `slug -> nombre visible`.
|
||||
|
||||
Formato objeto:
|
||||
|
||||
```json
|
||||
{
|
||||
"2xko": "2XKO",
|
||||
"tekken-8": "Tekken 8"
|
||||
}
|
||||
```
|
||||
|
||||
También se acepta array de objetos:
|
||||
|
||||
```json
|
||||
[
|
||||
{ "slug": "2xko", "title": "2XKO" },
|
||||
{ "slug": "tekken-8", "title": "Tekken 8" }
|
||||
]
|
||||
```
|
||||
|
||||
## Logos en servidor HTTP (sin logos locales en el bundle)
|
||||
|
||||
La vista de "Game Assets" carga los logos directamente desde:
|
||||
|
||||
@@ -5,6 +5,7 @@ import { nodecg } from './util/nodecg.js';
|
||||
|
||||
const CHARACTER_NAMES_FILE = 'fighting-characters.json';
|
||||
const LOCAL_MANIFEST_FILE = 'manifest.json';
|
||||
const GAME_TITLES_FILE = 'games.json';
|
||||
|
||||
type RemoteGame = {
|
||||
title: string;
|
||||
@@ -25,6 +26,13 @@ type HttpManifestEntry = string | {
|
||||
url?: unknown;
|
||||
};
|
||||
|
||||
type HttpGameTitleEntry = {
|
||||
slug?: unknown;
|
||||
title?: unknown;
|
||||
};
|
||||
|
||||
type HttpGameTitlesFile = Record<string, unknown> | HttpGameTitleEntry[];
|
||||
|
||||
const extensionDir = path.dirname(fileURLToPath(import.meta.url));
|
||||
const bundleRoot = path.resolve(extensionDir, '..');
|
||||
const assetsRoot = path.join(bundleRoot, 'game-assets');
|
||||
@@ -124,9 +132,61 @@ const titleFromSlug = (slug: string) => slug
|
||||
.map((segment) => segment[0].toUpperCase() + segment.slice(1))
|
||||
.join(' ');
|
||||
|
||||
const parseGameTitlesMap = (payload: unknown): Map<string, string> => {
|
||||
const map = new Map<string, string>();
|
||||
|
||||
if (Array.isArray(payload)) {
|
||||
for (const entry of payload) {
|
||||
const parsedEntry = entry as HttpGameTitleEntry;
|
||||
if (
|
||||
typeof entry === 'object'
|
||||
&& entry !== null
|
||||
&& typeof parsedEntry.slug === 'string'
|
||||
&& typeof parsedEntry.title === 'string'
|
||||
) {
|
||||
const slug = parsedEntry.slug.trim();
|
||||
const title = parsedEntry.title.trim();
|
||||
if (slug && title) {
|
||||
map.set(slug, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
if (typeof payload === 'object' && payload !== null) {
|
||||
for (const [slug, value] of Object.entries(payload)) {
|
||||
if (typeof value !== 'string') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const normalizedSlug = slug.trim();
|
||||
const title = value.trim();
|
||||
if (normalizedSlug && title) {
|
||||
map.set(normalizedSlug, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
};
|
||||
|
||||
const fetchCustomGameTitles = async (): Promise<Map<string, string>> => {
|
||||
const baseUrl = getConfiguredAssetsBaseUrl();
|
||||
const url = `${baseUrl}/games/${GAME_TITLES_FILE}`;
|
||||
|
||||
try {
|
||||
const payload = await fetchJson<HttpGameTitlesFile>(url);
|
||||
return parseGameTitlesMap(payload);
|
||||
} catch {
|
||||
return new Map<string, string>();
|
||||
}
|
||||
};
|
||||
|
||||
const listRemoteGames = async (): Promise<RemoteGame[]> => {
|
||||
const baseUrl = getConfiguredAssetsBaseUrl();
|
||||
const gamesIndexUrl = `${baseUrl}/games/`;
|
||||
const customTitles = await fetchCustomGameTitles();
|
||||
const response = await fetch(gamesIndexUrl, { headers: requestHeaders });
|
||||
if (!response.ok) {
|
||||
throw new Error(`Error HTTP (${response.status}) al solicitar ${gamesIndexUrl}`);
|
||||
@@ -153,7 +213,7 @@ const listRemoteGames = async (): Promise<RemoteGame[]> => {
|
||||
return uniqueSlugs.map((slug) => ({
|
||||
slug,
|
||||
repoFolder: slug,
|
||||
title: titleFromSlug(slug),
|
||||
title: customTitles.get(slug) ?? titleFromSlug(slug),
|
||||
logoFile: `${slug}.png`,
|
||||
}));
|
||||
};
|
||||
@@ -217,10 +277,11 @@ const listInstalledCharacterNamesByGame = async () => {
|
||||
};
|
||||
|
||||
const downloadGameAssets = async (gameSlug: string) => {
|
||||
const customTitles = await fetchCustomGameTitles();
|
||||
const game: RemoteGame = {
|
||||
slug: gameSlug,
|
||||
repoFolder: gameSlug,
|
||||
title: titleFromSlug(gameSlug),
|
||||
title: customTitles.get(gameSlug) ?? titleFromSlug(gameSlug),
|
||||
logoFile: `${gameSlug}.png`,
|
||||
};
|
||||
|
||||
@@ -269,11 +330,12 @@ const downloadGameAssets = async (gameSlug: string) => {
|
||||
};
|
||||
|
||||
const removeGameAssets = async (gameSlug: string) => {
|
||||
const customTitles = await fetchCustomGameTitles();
|
||||
const destinationFolder = path.join(assetsRoot, gameSlug);
|
||||
await rm(destinationFolder, { recursive: true, force: true });
|
||||
|
||||
return {
|
||||
title: titleFromSlug(gameSlug),
|
||||
title: customTitles.get(gameSlug) ?? titleFromSlug(gameSlug),
|
||||
slug: gameSlug,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user