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
|
```text
|
||||||
games/
|
games/
|
||||||
|
games.json (opcional, para nombres visibles personalizados)
|
||||||
street-fighter-6/
|
street-fighter-6/
|
||||||
street-fighter-6.png
|
street-fighter-6.png
|
||||||
manifest.json
|
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)
|
## Logos en servidor HTTP (sin logos locales en el bundle)
|
||||||
|
|
||||||
La vista de "Game Assets" carga los logos directamente desde:
|
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 CHARACTER_NAMES_FILE = 'fighting-characters.json';
|
||||||
const LOCAL_MANIFEST_FILE = 'manifest.json';
|
const LOCAL_MANIFEST_FILE = 'manifest.json';
|
||||||
|
const GAME_TITLES_FILE = 'games.json';
|
||||||
|
|
||||||
type RemoteGame = {
|
type RemoteGame = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -25,6 +26,13 @@ type HttpManifestEntry = string | {
|
|||||||
url?: unknown;
|
url?: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type HttpGameTitleEntry = {
|
||||||
|
slug?: unknown;
|
||||||
|
title?: unknown;
|
||||||
|
};
|
||||||
|
|
||||||
|
type HttpGameTitlesFile = Record<string, unknown> | HttpGameTitleEntry[];
|
||||||
|
|
||||||
const extensionDir = path.dirname(fileURLToPath(import.meta.url));
|
const extensionDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const bundleRoot = path.resolve(extensionDir, '..');
|
const bundleRoot = path.resolve(extensionDir, '..');
|
||||||
const assetsRoot = path.join(bundleRoot, 'game-assets');
|
const assetsRoot = path.join(bundleRoot, 'game-assets');
|
||||||
@@ -124,9 +132,61 @@ const titleFromSlug = (slug: string) => slug
|
|||||||
.map((segment) => segment[0].toUpperCase() + segment.slice(1))
|
.map((segment) => segment[0].toUpperCase() + segment.slice(1))
|
||||||
.join(' ');
|
.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 listRemoteGames = async (): Promise<RemoteGame[]> => {
|
||||||
const baseUrl = getConfiguredAssetsBaseUrl();
|
const baseUrl = getConfiguredAssetsBaseUrl();
|
||||||
const gamesIndexUrl = `${baseUrl}/games/`;
|
const gamesIndexUrl = `${baseUrl}/games/`;
|
||||||
|
const customTitles = await fetchCustomGameTitles();
|
||||||
const response = await fetch(gamesIndexUrl, { headers: requestHeaders });
|
const response = await fetch(gamesIndexUrl, { headers: requestHeaders });
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Error HTTP (${response.status}) al solicitar ${gamesIndexUrl}`);
|
throw new Error(`Error HTTP (${response.status}) al solicitar ${gamesIndexUrl}`);
|
||||||
@@ -153,7 +213,7 @@ const listRemoteGames = async (): Promise<RemoteGame[]> => {
|
|||||||
return uniqueSlugs.map((slug) => ({
|
return uniqueSlugs.map((slug) => ({
|
||||||
slug,
|
slug,
|
||||||
repoFolder: slug,
|
repoFolder: slug,
|
||||||
title: titleFromSlug(slug),
|
title: customTitles.get(slug) ?? titleFromSlug(slug),
|
||||||
logoFile: `${slug}.png`,
|
logoFile: `${slug}.png`,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
@@ -217,10 +277,11 @@ const listInstalledCharacterNamesByGame = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const downloadGameAssets = async (gameSlug: string) => {
|
const downloadGameAssets = async (gameSlug: string) => {
|
||||||
|
const customTitles = await fetchCustomGameTitles();
|
||||||
const game: RemoteGame = {
|
const game: RemoteGame = {
|
||||||
slug: gameSlug,
|
slug: gameSlug,
|
||||||
repoFolder: gameSlug,
|
repoFolder: gameSlug,
|
||||||
title: titleFromSlug(gameSlug),
|
title: customTitles.get(gameSlug) ?? titleFromSlug(gameSlug),
|
||||||
logoFile: `${gameSlug}.png`,
|
logoFile: `${gameSlug}.png`,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -269,11 +330,12 @@ const downloadGameAssets = async (gameSlug: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const removeGameAssets = async (gameSlug: string) => {
|
const removeGameAssets = async (gameSlug: string) => {
|
||||||
|
const customTitles = await fetchCustomGameTitles();
|
||||||
const destinationFolder = path.join(assetsRoot, gameSlug);
|
const destinationFolder = path.join(assetsRoot, gameSlug);
|
||||||
await rm(destinationFolder, { recursive: true, force: true });
|
await rm(destinationFolder, { recursive: true, force: true });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: titleFromSlug(gameSlug),
|
title: customTitles.get(gameSlug) ?? titleFromSlug(gameSlug),
|
||||||
slug: gameSlug,
|
slug: gameSlug,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user