IGDB-Integration für Game Library + Electron Update-Button im Version-Modal
- Neues IGDB-Service-Modul (igdb.ts): Token-Management, Rate-Limiting, Game-Lookup per Steam-AppID/Name, Batch-Enrichment mit In-Memory-Cache - Server: 2 neue Routes (/igdb/enrich/:steamId, /igdb/game/:appid), Auto-Enrichment bei Steam-Login und Refresh - Frontend: IGDB-Cover, Genre-Tags, Plattform-Badges, farbcodiertes Rating, IGDB-Anreichern-Button - Version-Modal: Update-Button für Electron-App (checkForUpdates/installUpdate), Desktop-App-Version anzeigen - CSS: Styles für IGDB-Elemente und Update-UI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
aec1142bff
commit
b404c20eca
6 changed files with 785 additions and 25 deletions
|
|
@ -3,6 +3,7 @@ import type { Plugin, PluginContext } from '../../core/plugin.js';
|
|||
import { sseBroadcast } from '../../core/sse.js';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { lookupGame, enrichGames, type IgdbGameInfo } from './igdb.js';
|
||||
|
||||
// ── Types ──
|
||||
|
||||
|
|
@ -11,6 +12,7 @@ interface SteamGame {
|
|||
name: string;
|
||||
playtime_forever: number; // minutes
|
||||
img_icon_url: string;
|
||||
igdb?: IgdbGameInfo;
|
||||
}
|
||||
|
||||
interface SteamUser {
|
||||
|
|
@ -185,6 +187,21 @@ const gameLibraryPlugin: Plugin = {
|
|||
saveData(ctx, data);
|
||||
broadcastUpdate(data);
|
||||
|
||||
// Fire-and-forget IGDB enrichment
|
||||
enrichGames(games.map(g => ({ appid: g.appid, name: g.name }))).then(igdbMap => {
|
||||
const data2 = loadData(ctx);
|
||||
const user2 = data2.users[steamId];
|
||||
if (user2) {
|
||||
let count = 0;
|
||||
for (const game of user2.games) {
|
||||
const info = igdbMap.get(game.appid);
|
||||
if (info) { game.igdb = info; count++; }
|
||||
}
|
||||
saveData(ctx, data2);
|
||||
console.log(`[GameLibrary] IGDB auto-enrichment: ${count}/${games.length} games`);
|
||||
}
|
||||
}).catch(err => console.error('[GameLibrary] IGDB auto-enrichment error:', err));
|
||||
|
||||
const personaName = profile.personaName;
|
||||
console.log(`[GameLibrary] Steam-Benutzer verknuepft: ${personaName} (${steamId}) - ${games.length} Spiele`);
|
||||
|
||||
|
|
@ -283,6 +300,7 @@ const gameLibraryPlugin: Plugin = {
|
|||
appid: refGame.appid,
|
||||
name: refGame.name,
|
||||
img_icon_url: refGame.img_icon_url,
|
||||
...(refGame.igdb ? { igdb: refGame.igdb } : {}),
|
||||
owners,
|
||||
};
|
||||
});
|
||||
|
|
@ -330,6 +348,89 @@ const gameLibraryPlugin: Plugin = {
|
|||
res.json({ games, count: games.length });
|
||||
});
|
||||
|
||||
// ── GET /api/game-library/igdb/enrich/:steamId ──
|
||||
app.get('/api/game-library/igdb/enrich/:steamId', async (req, res) => {
|
||||
try {
|
||||
const { steamId } = req.params;
|
||||
const force = req.query.force === 'true';
|
||||
const data = loadData(ctx);
|
||||
const user = data.users[steamId];
|
||||
if (!user) {
|
||||
res.status(404).json({ error: 'Benutzer nicht gefunden.' });
|
||||
return;
|
||||
}
|
||||
|
||||
const gamesToEnrich = force
|
||||
? user.games.map(g => ({ appid: g.appid, name: g.name }))
|
||||
: user.games.filter(g => !g.igdb).map(g => ({ appid: g.appid, name: g.name }));
|
||||
|
||||
const igdbMap = await enrichGames(gamesToEnrich);
|
||||
|
||||
// Reload data to avoid stale writes
|
||||
const freshData = loadData(ctx);
|
||||
const freshUser = freshData.users[steamId];
|
||||
if (!freshUser) {
|
||||
res.status(404).json({ error: 'Benutzer nicht gefunden.' });
|
||||
return;
|
||||
}
|
||||
|
||||
let enriched = 0;
|
||||
for (const game of freshUser.games) {
|
||||
const info = igdbMap.get(game.appid);
|
||||
if (info) {
|
||||
game.igdb = info;
|
||||
enriched++;
|
||||
}
|
||||
}
|
||||
|
||||
saveData(ctx, freshData);
|
||||
console.log(`[GameLibrary] IGDB enrichment for ${freshUser.personaName}: ${enriched}/${freshUser.games.length} games`);
|
||||
|
||||
res.json({ enriched, total: freshUser.games.length });
|
||||
} catch (err) {
|
||||
console.error('[GameLibrary] IGDB enrich error:', err);
|
||||
res.status(500).json({ error: 'Fehler bei der IGDB-Anreicherung.' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── GET /api/game-library/igdb/game/:appid ──
|
||||
app.get('/api/game-library/igdb/game/:appid', async (req, res) => {
|
||||
try {
|
||||
const appid = parseInt(req.params.appid, 10);
|
||||
if (isNaN(appid)) {
|
||||
res.status(400).json({ error: 'Ungueltige App-ID.' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Find game name from any user's library
|
||||
const data = loadData(ctx);
|
||||
let gameName = '';
|
||||
for (const user of Object.values(data.users)) {
|
||||
const game = user.games.find(g => g.appid === appid);
|
||||
if (game) {
|
||||
gameName = game.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gameName) {
|
||||
res.status(404).json({ error: 'Spiel nicht in der Bibliothek gefunden.' });
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await lookupGame(appid, gameName);
|
||||
if (!info) {
|
||||
res.status(404).json({ error: 'Spiel nicht in IGDB gefunden.' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json(info);
|
||||
} catch (err) {
|
||||
console.error('[GameLibrary] IGDB game lookup error:', err);
|
||||
res.status(500).json({ error: 'Fehler bei der IGDB-Abfrage.' });
|
||||
}
|
||||
});
|
||||
|
||||
// ── POST /api/game-library/refresh/:steamId ──
|
||||
app.post('/api/game-library/refresh/:steamId', async (req, res) => {
|
||||
try {
|
||||
|
|
@ -358,6 +459,21 @@ const gameLibraryPlugin: Plugin = {
|
|||
saveData(ctx, data);
|
||||
broadcastUpdate(data);
|
||||
|
||||
// Fire-and-forget IGDB enrichment
|
||||
enrichGames(games.map(g => ({ appid: g.appid, name: g.name }))).then(igdbMap => {
|
||||
const data2 = loadData(ctx);
|
||||
const user2 = data2.users[steamId];
|
||||
if (user2) {
|
||||
let count = 0;
|
||||
for (const game of user2.games) {
|
||||
const info = igdbMap.get(game.appid);
|
||||
if (info) { game.igdb = info; count++; }
|
||||
}
|
||||
saveData(ctx, data2);
|
||||
console.log(`[GameLibrary] IGDB auto-enrichment: ${count}/${games.length} games`);
|
||||
}
|
||||
}).catch(err => console.error('[GameLibrary] IGDB auto-enrichment error:', err));
|
||||
|
||||
console.log(`[GameLibrary] Aktualisiert: ${profile.personaName} (${steamId}) - ${games.length} Spiele`);
|
||||
|
||||
res.json({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue