mirror of
https://github.com/Pandipipas/scoreko-dev.git
synced 2026-06-06 03:32:06 +00:00
feat: cargar juegos de assets desde servidor HTTP
This commit is contained in:
@@ -9,6 +9,13 @@ type ProgressPayload = {
|
|||||||
status: DownloadStatus;
|
status: DownloadStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type RemoteGame = {
|
||||||
|
title: string;
|
||||||
|
slug: string;
|
||||||
|
repoFolder: string;
|
||||||
|
logoFile: string;
|
||||||
|
};
|
||||||
|
|
||||||
const sendNodecgMessage = <TResponse>(messageName: string, payload?: unknown) => new Promise<TResponse>((resolve, reject) => {
|
const sendNodecgMessage = <TResponse>(messageName: string, payload?: unknown) => new Promise<TResponse>((resolve, reject) => {
|
||||||
nodecg.sendMessage(messageName, payload, (error: unknown, response: unknown) => {
|
nodecg.sendMessage(messageName, payload, (error: unknown, response: unknown) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -24,6 +31,7 @@ let progressListenerAttached = false;
|
|||||||
|
|
||||||
export const useGameAssetsStore = defineStore('game-assets', () => {
|
export const useGameAssetsStore = defineStore('game-assets', () => {
|
||||||
const installedGames = ref<string[]>([]);
|
const installedGames = ref<string[]>([]);
|
||||||
|
const availableGames = ref<RemoteGame[]>([]);
|
||||||
const characterNamesByGame = ref<Record<string, string[]>>({});
|
const characterNamesByGame = ref<Record<string, string[]>>({});
|
||||||
const loadingByTitle = ref<Record<string, boolean>>({});
|
const loadingByTitle = ref<Record<string, boolean>>({});
|
||||||
const removingByTitle = ref<Record<string, boolean>>({});
|
const removingByTitle = ref<Record<string, boolean>>({});
|
||||||
@@ -62,6 +70,8 @@ export const useGameAssetsStore = defineStore('game-assets', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const refreshInstalledGames = async () => {
|
const refreshInstalledGames = async () => {
|
||||||
|
const availableResponse = await sendNodecgMessage<RemoteGame[]>('scoreko-assets:listRemoteGames');
|
||||||
|
availableGames.value = Array.isArray(availableResponse) ? availableResponse : [];
|
||||||
const response = await sendNodecgMessage<string[]>('scoreko-assets:listInstalled');
|
const response = await sendNodecgMessage<string[]>('scoreko-assets:listInstalled');
|
||||||
installedGames.value = Array.isArray(response) ? response : [];
|
installedGames.value = Array.isArray(response) ? response : [];
|
||||||
const configResponse = await sendNodecgMessage<{ assetsBaseUrl?: string }>('scoreko-assets:getAssetsBaseUrl');
|
const configResponse = await sendNodecgMessage<{ assetsBaseUrl?: string }>('scoreko-assets:getAssetsBaseUrl');
|
||||||
@@ -72,59 +82,60 @@ export const useGameAssetsStore = defineStore('game-assets', () => {
|
|||||||
return installedGames.value;
|
return installedGames.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadGame = async (title: string) => {
|
const downloadGame = async (slug: string) => {
|
||||||
loadingByTitle.value = {
|
loadingByTitle.value = {
|
||||||
...loadingByTitle.value,
|
...loadingByTitle.value,
|
||||||
[title]: true,
|
[slug]: true,
|
||||||
};
|
};
|
||||||
progressByTitle.value = {
|
progressByTitle.value = {
|
||||||
...progressByTitle.value,
|
...progressByTitle.value,
|
||||||
[title]: 0,
|
[slug]: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await sendNodecgMessage<{ installedGames: string[] }>('scoreko-assets:downloadGame', { title });
|
const response = await sendNodecgMessage<{ installedGames: string[] }>('scoreko-assets:downloadGame', { slug });
|
||||||
installedGames.value = response.installedGames;
|
installedGames.value = response.installedGames;
|
||||||
await refreshCharacterNamesByGame();
|
await refreshCharacterNamesByGame();
|
||||||
loadingByTitle.value = {
|
loadingByTitle.value = {
|
||||||
...loadingByTitle.value,
|
...loadingByTitle.value,
|
||||||
[title]: false,
|
[slug]: false,
|
||||||
};
|
};
|
||||||
progressByTitle.value = {
|
progressByTitle.value = {
|
||||||
...progressByTitle.value,
|
...progressByTitle.value,
|
||||||
[title]: 100,
|
[slug]: 100,
|
||||||
};
|
};
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
loadingByTitle.value = {
|
loadingByTitle.value = {
|
||||||
...loadingByTitle.value,
|
...loadingByTitle.value,
|
||||||
[title]: false,
|
[slug]: false,
|
||||||
};
|
};
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeGame = async (title: string) => {
|
const removeGame = async (slug: string) => {
|
||||||
removingByTitle.value = {
|
removingByTitle.value = {
|
||||||
...removingByTitle.value,
|
...removingByTitle.value,
|
||||||
[title]: true,
|
[slug]: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await sendNodecgMessage<{ installedGames: string[] }>('scoreko-assets:removeGame', { title });
|
const response = await sendNodecgMessage<{ installedGames: string[] }>('scoreko-assets:removeGame', { slug });
|
||||||
installedGames.value = response.installedGames;
|
installedGames.value = response.installedGames;
|
||||||
await refreshCharacterNamesByGame();
|
await refreshCharacterNamesByGame();
|
||||||
return response;
|
return response;
|
||||||
} finally {
|
} finally {
|
||||||
removingByTitle.value = {
|
removingByTitle.value = {
|
||||||
...removingByTitle.value,
|
...removingByTitle.value,
|
||||||
[title]: false,
|
[slug]: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
installedGames,
|
installedGames,
|
||||||
|
availableGames,
|
||||||
characterNamesByGame,
|
characterNamesByGame,
|
||||||
loadingByTitle,
|
loadingByTitle,
|
||||||
removingByTitle,
|
removingByTitle,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { useGameAssetsStore } from '../stores/game-assets';
|
import { useGameAssetsStore } from '../stores/game-assets';
|
||||||
import { fightingGamesCatalog } from '../../../shared/fighting-games';
|
|
||||||
|
|
||||||
const gameAssetsStore = useGameAssetsStore();
|
const gameAssetsStore = useGameAssetsStore();
|
||||||
const errorMessage = ref('');
|
const errorMessage = ref('');
|
||||||
@@ -18,13 +17,13 @@ const getGameLogoUrl = (repoFolder: string, logoFile: string) => {
|
|||||||
const normalizedSearch = computed(() => search.value.trim().toLowerCase());
|
const normalizedSearch = computed(() => search.value.trim().toLowerCase());
|
||||||
const filteredGames = computed(() => {
|
const filteredGames = computed(() => {
|
||||||
if (!normalizedSearch.value) {
|
if (!normalizedSearch.value) {
|
||||||
return fightingGamesCatalog;
|
return gameAssetsStore.availableGames;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fightingGamesCatalog.filter((game) => game.title.toLowerCase().includes(normalizedSearch.value));
|
return gameAssetsStore.availableGames.filter((game) => game.title.toLowerCase().includes(normalizedSearch.value));
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectedGame = computed(() => fightingGamesCatalog.find((game) => game.slug === selectedGameSlug.value) ?? null);
|
const selectedGame = computed(() => gameAssetsStore.availableGames.find((game) => game.slug === selectedGameSlug.value) ?? null);
|
||||||
const installedGameSet = computed(() => new Set(gameAssetsStore.installedGames));
|
const installedGameSet = computed(() => new Set(gameAssetsStore.installedGames));
|
||||||
|
|
||||||
const openGameDialog = (slug: string) => {
|
const openGameDialog = (slug: string) => {
|
||||||
@@ -35,25 +34,21 @@ const closeGameDialog = () => {
|
|||||||
selectedGameSlug.value = null;
|
selectedGameSlug.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const openTrailer = (trailerUrl: string) => {
|
const downloadGameBySlug = async (slug: string) => {
|
||||||
window.open(trailerUrl, '_blank', 'noopener,noreferrer');
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadGameByTitle = async (title: string) => {
|
|
||||||
errorMessage.value = '';
|
errorMessage.value = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await gameAssetsStore.downloadGame(title);
|
await gameAssetsStore.downloadGame(slug);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error instanceof Error ? error.message : 'No se pudieron descargar los assets.';
|
errorMessage.value = error instanceof Error ? error.message : 'No se pudieron descargar los assets.';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeGameByTitle = async (title: string) => {
|
const removeGameBySlug = async (slug: string) => {
|
||||||
errorMessage.value = '';
|
errorMessage.value = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await gameAssetsStore.removeGame(title);
|
await gameAssetsStore.removeGame(slug);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error instanceof Error ? error.message : 'No se pudieron borrar los assets.';
|
errorMessage.value = error instanceof Error ? error.message : 'No se pudieron borrar los assets.';
|
||||||
}
|
}
|
||||||
@@ -64,9 +59,9 @@ const onDownloadFromDialog = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetTitle = selectedGame.value.title;
|
const targetSlug = selectedGame.value.slug;
|
||||||
closeGameDialog();
|
closeGameDialog();
|
||||||
await downloadGameByTitle(targetTitle);
|
await downloadGameBySlug(targetSlug);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@@ -114,9 +109,9 @@ onMounted(async () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="gameAssetsStore.loadingByTitle[game.title]"
|
v-if="gameAssetsStore.loadingByTitle[game.slug]"
|
||||||
class="download-overlay"
|
class="download-overlay"
|
||||||
:style="{ '--progress-width': `${gameAssetsStore.progressByTitle[game.title] ?? 0}%` }"
|
:style="{ '--progress-width': `${gameAssetsStore.progressByTitle[game.slug] ?? 0}%` }"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="tile-actions">
|
<div class="tile-actions">
|
||||||
@@ -131,7 +126,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<div class="row items-center no-wrap q-gutter-sm">
|
<div class="row items-center no-wrap q-gutter-sm">
|
||||||
<QBtn
|
<QBtn
|
||||||
v-if="installedGameSet.has(game.title)"
|
v-if="installedGameSet.has(game.slug)"
|
||||||
flat
|
flat
|
||||||
round
|
round
|
||||||
size="md"
|
size="md"
|
||||||
@@ -145,11 +140,11 @@ onMounted(async () => {
|
|||||||
flat
|
flat
|
||||||
round
|
round
|
||||||
size="md"
|
size="md"
|
||||||
:icon="gameAssetsStore.loadingByTitle[game.title] ? 'autorenew' : 'download'"
|
:icon="gameAssetsStore.loadingByTitle[game.slug] ? 'autorenew' : 'download'"
|
||||||
:class="{ 'downloading-spin': gameAssetsStore.loadingByTitle[game.title] }"
|
:class="{ 'downloading-spin': gameAssetsStore.loadingByTitle[game.slug] }"
|
||||||
color="white"
|
color="white"
|
||||||
:disable="gameAssetsStore.loadingByTitle[game.title]"
|
:disable="gameAssetsStore.loadingByTitle[game.slug]"
|
||||||
@click="downloadGameByTitle(game.title)"
|
@click="downloadGameBySlug(game.slug)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,34 +171,24 @@ onMounted(async () => {
|
|||||||
{{ selectedGame.title }}
|
{{ selectedGame.title }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-body2 q-mb-sm">
|
<div class="text-body2 q-mb-sm">
|
||||||
{{ selectedGame.description }}
|
Carpeta en servidor: <strong>{{ selectedGame.repoFolder }}</strong>.
|
||||||
</div>
|
|
||||||
<div class="text-caption text-weight-medium q-mb-md">
|
|
||||||
Peso aprox: {{ selectedGame.approxSize }}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row q-gutter-sm justify-end">
|
<div class="row q-gutter-sm justify-end">
|
||||||
<QBtn
|
<QBtn
|
||||||
v-if="installedGameSet.has(selectedGame.title)"
|
v-if="installedGameSet.has(selectedGame.slug)"
|
||||||
flat
|
flat
|
||||||
color="negative"
|
color="negative"
|
||||||
icon="delete"
|
icon="delete"
|
||||||
:loading="gameAssetsStore.removingByTitle[selectedGame.title]"
|
:loading="gameAssetsStore.removingByTitle[selectedGame.slug]"
|
||||||
label="Borrar assets"
|
label="Borrar assets"
|
||||||
@click="removeGameByTitle(selectedGame.title); closeGameDialog()"
|
@click="removeGameBySlug(selectedGame.slug); closeGameDialog()"
|
||||||
/>
|
|
||||||
<QBtn
|
|
||||||
flat
|
|
||||||
color="secondary"
|
|
||||||
icon="smart_display"
|
|
||||||
label="Trailer"
|
|
||||||
@click="openTrailer(selectedGame.trailerUrl)"
|
|
||||||
/>
|
/>
|
||||||
<QBtn
|
<QBtn
|
||||||
color="primary"
|
color="primary"
|
||||||
icon="download"
|
icon="download"
|
||||||
:disable="installedGameSet.has(selectedGame.title)"
|
:disable="installedGameSet.has(selectedGame.slug)"
|
||||||
:label="installedGameSet.has(selectedGame.title) ? 'Descargado' : 'Descargar assets'"
|
:label="installedGameSet.has(selectedGame.slug) ? 'Descargado' : 'Descargar assets'"
|
||||||
@click="onDownloadFromDialog"
|
@click="onDownloadFromDialog"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,14 +6,12 @@ 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 gameCatalog = [
|
type RemoteGame = {
|
||||||
{ title: 'Street Fighter 6', slug: 'street-fighter-6', repoFolder: 'street-fighter-6' },
|
title: string;
|
||||||
{ title: 'TEKKEN 8', slug: 'tekken-8', repoFolder: 'tekken-8' },
|
slug: string;
|
||||||
{ title: '2XKO', slug: '2xko', repoFolder: '2xko' },
|
repoFolder: string;
|
||||||
{ title: 'Guilty Gear -Strive-', slug: 'guilty-gear-strive', repoFolder: 'guilty-gear-strive' },
|
logoFile: string;
|
||||||
{ title: 'Mortal Kombat 1', slug: 'mortal-kombat-1', repoFolder: 'mortal-kombat-1' },
|
};
|
||||||
{ title: 'THE KING OF FIGHTERS XV', slug: 'the-king-of-fighters-xv', repoFolder: 'the-king-of-fighters-xv' },
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
type AssetFileEntry = {
|
type AssetFileEntry = {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -120,7 +118,47 @@ const normalizeManifestEntry = (entry: HttpManifestEntry, gameTitle: string) =>
|
|||||||
throw new Error(`El ${LOCAL_MANIFEST_FILE} de ${gameTitle} contiene entradas inválidas.`);
|
throw new Error(`El ${LOCAL_MANIFEST_FILE} de ${gameTitle} contiene entradas inválidas.`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const listHttpFiles = async (game: (typeof gameCatalog)[number]): Promise<AssetFileEntry[]> => {
|
const titleFromSlug = (slug: string) => slug
|
||||||
|
.split('-')
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((segment) => segment[0].toUpperCase() + segment.slice(1))
|
||||||
|
.join(' ');
|
||||||
|
|
||||||
|
const listRemoteGames = async (): Promise<RemoteGame[]> => {
|
||||||
|
const baseUrl = getConfiguredAssetsBaseUrl();
|
||||||
|
const gamesIndexUrl = `${baseUrl}/games/`;
|
||||||
|
const response = await fetch(gamesIndexUrl, { headers: requestHeaders });
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Error HTTP (${response.status}) al solicitar ${gamesIndexUrl}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await response.text();
|
||||||
|
const hrefMatches = [...html.matchAll(/href=["']([^"']+)["']/gi)].map((match) => match[1]);
|
||||||
|
const slugs = hrefMatches
|
||||||
|
.map((href) => {
|
||||||
|
const withoutQuery = href.split('?')[0]?.split('#')[0] ?? '';
|
||||||
|
if (!withoutQuery.endsWith('/')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const decoded = decodeURIComponent(withoutQuery);
|
||||||
|
const trimmed = decoded.replace(/^\/+|\/+$/g, '');
|
||||||
|
if (!trimmed || trimmed.includes('/') || trimmed === '.' || trimmed === '..') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
})
|
||||||
|
.filter((slug): slug is string => slug !== null);
|
||||||
|
|
||||||
|
const uniqueSlugs = [...new Set(slugs)].sort((left, right) => left.localeCompare(right));
|
||||||
|
return uniqueSlugs.map((slug) => ({
|
||||||
|
slug,
|
||||||
|
repoFolder: slug,
|
||||||
|
title: titleFromSlug(slug),
|
||||||
|
logoFile: `${slug}.png`,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const listHttpFiles = async (game: RemoteGame): Promise<AssetFileEntry[]> => {
|
||||||
const baseUrl = getConfiguredAssetsBaseUrl();
|
const baseUrl = getConfiguredAssetsBaseUrl();
|
||||||
const manifestUrl = `${baseUrl}/games/${game.repoFolder}/${LOCAL_MANIFEST_FILE}`;
|
const manifestUrl = `${baseUrl}/games/${game.repoFolder}/${LOCAL_MANIFEST_FILE}`;
|
||||||
const entries = await fetchJson<HttpManifestEntry[]>(manifestUrl);
|
const entries = await fetchJson<HttpManifestEntry[]>(manifestUrl);
|
||||||
@@ -143,8 +181,7 @@ const listHttpFiles = async (game: (typeof gameCatalog)[number]): Promise<AssetF
|
|||||||
|
|
||||||
const listInstalledGames = async () => {
|
const listInstalledGames = async () => {
|
||||||
const entries = await readdir(assetsRoot, { withFileTypes: true }).catch(() => []);
|
const entries = await readdir(assetsRoot, { withFileTypes: true }).catch(() => []);
|
||||||
const installedSlugs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
|
||||||
return gameCatalog.filter((game) => installedSlugs.includes(game.slug)).map((game) => game.title);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseCharacterNames = (content: string, gameTitle: string) => {
|
const parseCharacterNames = (content: string, gameTitle: string) => {
|
||||||
@@ -163,28 +200,31 @@ const parseCharacterNames = (content: string, gameTitle: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const listInstalledCharacterNamesByGame = async () => {
|
const listInstalledCharacterNamesByGame = async () => {
|
||||||
const charactersByGame = await Promise.all(gameCatalog.map(async (game) => {
|
const installedGames = await listInstalledGames();
|
||||||
const sourcePath = path.join(assetsRoot, game.slug, CHARACTER_NAMES_FILE);
|
const charactersByGame = await Promise.all(installedGames.map(async (slug) => {
|
||||||
|
const sourcePath = path.join(assetsRoot, slug, CHARACTER_NAMES_FILE);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fileContent = await readFile(sourcePath, 'utf8');
|
const fileContent = await readFile(sourcePath, 'utf8');
|
||||||
const names = parseCharacterNames(fileContent, game.title);
|
const names = parseCharacterNames(fileContent, slug);
|
||||||
return [game.title, names] as const;
|
return [slug, names] as const;
|
||||||
} catch {
|
} catch {
|
||||||
return [game.title, []] as const;
|
return [slug, []] as const;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return Object.fromEntries(charactersByGame) as Record<string, string[]>;
|
return Object.fromEntries(charactersByGame) as Record<string, string[]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadGameAssets = async (gameTitle: string) => {
|
const downloadGameAssets = async (gameSlug: string) => {
|
||||||
const game = gameCatalog.find((entry) => entry.title === gameTitle);
|
const game: RemoteGame = {
|
||||||
if (!game) {
|
slug: gameSlug,
|
||||||
throw new Error('Juego no soportado en el catálogo.');
|
repoFolder: gameSlug,
|
||||||
}
|
title: titleFromSlug(gameSlug),
|
||||||
|
logoFile: `${gameSlug}.png`,
|
||||||
|
};
|
||||||
|
|
||||||
emitProgress(game.title, 0, 'downloading');
|
emitProgress(game.slug, 0, 'downloading');
|
||||||
|
|
||||||
const files = await listHttpFiles(game);
|
const files = await listHttpFiles(game);
|
||||||
if (!files.length) {
|
if (!files.length) {
|
||||||
@@ -217,10 +257,10 @@ const downloadGameAssets = async (gameTitle: string) => {
|
|||||||
|
|
||||||
downloadedBytes += file.size || 0;
|
downloadedBytes += file.size || 0;
|
||||||
const progress = totalBytes > 0 ? Math.round((downloadedBytes / totalBytes) * 100) : 100;
|
const progress = totalBytes > 0 ? Math.round((downloadedBytes / totalBytes) * 100) : 100;
|
||||||
emitProgress(game.title, progress, 'downloading');
|
emitProgress(game.slug, progress, 'downloading');
|
||||||
}
|
}
|
||||||
|
|
||||||
emitProgress(game.title, 100, 'completed');
|
emitProgress(game.slug, 100, 'completed');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: game.title,
|
title: game.title,
|
||||||
@@ -228,21 +268,28 @@ const downloadGameAssets = async (gameTitle: string) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeGameAssets = async (gameTitle: string) => {
|
const removeGameAssets = async (gameSlug: string) => {
|
||||||
const game = gameCatalog.find((entry) => entry.title === gameTitle);
|
const destinationFolder = path.join(assetsRoot, gameSlug);
|
||||||
if (!game) {
|
|
||||||
throw new Error('Juego no soportado en el catálogo.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const destinationFolder = path.join(assetsRoot, game.slug);
|
|
||||||
await rm(destinationFolder, { recursive: true, force: true });
|
await rm(destinationFolder, { recursive: true, force: true });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: game.title,
|
title: titleFromSlug(gameSlug),
|
||||||
slug: game.slug,
|
slug: gameSlug,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nodecg.listenFor('scoreko-assets:listRemoteGames', async (_payload: unknown, ack) => {
|
||||||
|
if (typeof ack !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ack(null, await listRemoteGames());
|
||||||
|
} catch (error) {
|
||||||
|
ack((error as Error).message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
nodecg.listenFor('scoreko-assets:listInstalled', async (_payload: unknown, ack) => {
|
nodecg.listenFor('scoreko-assets:listInstalled', async (_payload: unknown, ack) => {
|
||||||
if (typeof ack !== 'function') {
|
if (typeof ack !== 'function') {
|
||||||
return;
|
return;
|
||||||
@@ -285,19 +332,19 @@ nodecg.listenFor('scoreko-assets:downloadGame', async (payload: unknown, ack) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const title = typeof payload === 'object' && payload !== null ? (payload as { title?: unknown }).title : undefined;
|
const slug = typeof payload === 'object' && payload !== null ? (payload as { slug?: unknown }).slug : undefined;
|
||||||
if (typeof title !== 'string') {
|
if (typeof slug !== 'string') {
|
||||||
throw new Error('Título de juego inválido.');
|
throw new Error('Slug de juego inválido.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloaded = await downloadGameAssets(title);
|
const downloaded = await downloadGameAssets(slug);
|
||||||
ack(null, {
|
ack(null, {
|
||||||
downloaded,
|
downloaded,
|
||||||
installedGames: await listInstalledGames(),
|
installedGames: await listInstalledGames(),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (typeof payload === 'object' && payload !== null && typeof (payload as { title?: unknown }).title === 'string') {
|
if (typeof payload === 'object' && payload !== null && typeof (payload as { slug?: unknown }).slug === 'string') {
|
||||||
emitProgress((payload as { title: string }).title, 0, 'error');
|
emitProgress((payload as { slug: string }).slug, 0, 'error');
|
||||||
}
|
}
|
||||||
ack((error as Error).message);
|
ack((error as Error).message);
|
||||||
}
|
}
|
||||||
@@ -309,12 +356,12 @@ nodecg.listenFor('scoreko-assets:removeGame', async (payload: unknown, ack) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const title = typeof payload === 'object' && payload !== null ? (payload as { title?: unknown }).title : undefined;
|
const slug = typeof payload === 'object' && payload !== null ? (payload as { slug?: unknown }).slug : undefined;
|
||||||
if (typeof title !== 'string') {
|
if (typeof slug !== 'string') {
|
||||||
throw new Error('Título de juego inválido.');
|
throw new Error('Slug de juego inválido.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const removed = await removeGameAssets(title);
|
const removed = await removeGameAssets(slug);
|
||||||
ack(null, {
|
ack(null, {
|
||||||
removed,
|
removed,
|
||||||
installedGames: await listInstalledGames(),
|
installedGames: await listInstalledGames(),
|
||||||
|
|||||||
Reference in New Issue
Block a user