From 4994d5c2454788f87e9f2c7bdf2981e9173753ed Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 8 Mar 2026 01:58:04 +0100 Subject: [PATCH] IGDB auto-enrichment: Server-Start + Frontend auto-trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Server enriched bestehende User beim Plugin-Start automatisch (fire-and-forget) - Frontend triggert IGDB-Enrichment automatisch beim Öffnen einer User-Bibliothek - Reduzierte Log-Ausgabe (kein Spam pro Cache-Hit mehr) - IGDB-Button zeigt Lade-Animation während Enrichment Co-Authored-By: Claude Opus 4.6 --- server/src/plugins/game-library/igdb.ts | 8 +++-- server/src/plugins/game-library/index.ts | 35 ++++++++++++++++++- .../plugins/game-library/GameLibraryTab.tsx | 22 ++++++++++-- web/src/plugins/game-library/game-library.css | 9 +++++ 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/server/src/plugins/game-library/igdb.ts b/server/src/plugins/game-library/igdb.ts index ff48eb2..67e3bef 100644 --- a/server/src/plugins/game-library/igdb.ts +++ b/server/src/plugins/game-library/igdb.ts @@ -271,14 +271,18 @@ export async function enrichGames( for (const game of games) { const cached = enrichmentCache.get(game.appid); if (cached) { - console.log(`[IGDB] Cache hit for appid ${game.appid} ("${cached.name}")`); result.set(game.appid, cached); } else { toFetch.push(game); } } - if (toFetch.length === 0) return result; + if (toFetch.length === 0) { + console.log(`[IGDB] All ${games.length} games found in cache`); + return result; + } + + console.log(`[IGDB] ${result.size} cache hits, ${toFetch.length} to fetch`); const BATCH_SIZE = 4; for (let i = 0; i < toFetch.length; i += BATCH_SIZE) { diff --git a/server/src/plugins/game-library/index.ts b/server/src/plugins/game-library/index.ts index a186f47..2812830 100644 --- a/server/src/plugins/game-library/index.ts +++ b/server/src/plugins/game-library/index.ts @@ -118,8 +118,41 @@ const gameLibraryPlugin: Plugin = { description: 'Steam Spielebibliothek', async init(ctx) { - loadData(ctx); // ensure file exists + const data = loadData(ctx); // ensure file exists console.log('[GameLibrary] Initialized'); + + // Fire-and-forget: auto-enrich all existing users with IGDB data + const allUsers = Object.values(data.users); + const unenrichedUsers = allUsers.filter(u => + u.games.some(g => !g.igdb), + ); + if (unenrichedUsers.length > 0) { + console.log(`[GameLibrary] Auto-enriching ${unenrichedUsers.length} user(s) with IGDB data...`); + (async () => { + for (const user of unenrichedUsers) { + try { + const unenrichedGames = user.games.filter(g => !g.igdb).map(g => ({ appid: g.appid, name: g.name })); + console.log(`[GameLibrary] IGDB auto-enriching ${user.personaName}: ${unenrichedGames.length} games...`); + const igdbMap = await enrichGames(unenrichedGames); + // Reload fresh to avoid stale writes + const freshData = loadData(ctx); + const freshUser = freshData.users[user.steamId]; + if (freshUser) { + let count = 0; + for (const game of freshUser.games) { + const info = igdbMap.get(game.appid); + if (info) { game.igdb = info; count++; } + } + saveData(ctx, freshData); + console.log(`[GameLibrary] IGDB startup enrichment for ${user.personaName}: ${count}/${unenrichedGames.length} games matched`); + } + } catch (err) { + console.error(`[GameLibrary] IGDB startup enrichment error for ${user.personaName}:`, err); + } + } + console.log('[GameLibrary] IGDB startup enrichment complete.'); + })(); + } }, registerRoutes(app: express.Application, ctx: PluginContext) { diff --git a/web/src/plugins/game-library/GameLibraryTab.tsx b/web/src/plugins/game-library/GameLibraryTab.tsx index 576946b..5bf164d 100644 --- a/web/src/plugins/game-library/GameLibraryTab.tsx +++ b/web/src/plugins/game-library/GameLibraryTab.tsx @@ -145,7 +145,23 @@ export default function GameLibraryTab({ data }: { data: any }) { const resp = await fetch(`/api/game-library/user/${steamId}`); if (resp.ok) { const d = await resp.json(); - setUserGames(d.games || d); + const games: SteamGame[] = d.games || d; + setUserGames(games); + + // Auto-enrich with IGDB if many games lack data + const unenriched = games.filter(g => !g.igdb).length; + if (unenriched > 0) { + setEnriching(steamId); + fetch(`/api/game-library/igdb/enrich/${steamId}`) + .then(r => r.ok ? r.json() : null) + .then(() => fetch(`/api/game-library/user/${steamId}`)) + .then(r => r.ok ? r.json() : null) + .then(d2 => { + if (d2) setUserGames(d2.games || d2); + }) + .catch(() => {}) + .finally(() => setEnriching(null)); + } } } catch { /* silent */ @@ -439,10 +455,10 @@ export default function GameLibraryTab({ data }: { data: any }) { ↻ diff --git a/web/src/plugins/game-library/game-library.css b/web/src/plugins/game-library/game-library.css index f8f0cd2..537df81 100644 --- a/web/src/plugins/game-library/game-library.css +++ b/web/src/plugins/game-library/game-library.css @@ -553,6 +553,15 @@ cursor: not-allowed; } +.gl-enrich-btn.enriching { + animation: gl-pulse 1.5s ease-in-out infinite; +} + +@keyframes gl-pulse { + 0%, 100% { opacity: 0.5; } + 50% { opacity: 1; } +} + /* ── Responsive ── */ @media (max-width: 768px) {