export interface SteamSummary { avatarUrl: string; personaName: string | null; } // Steam's generic "no avatar set" question-mark image — used whenever a // player has no avatar, or the lookup fails for any reason. export const DEFAULT_AVATAR_URL = 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/fe/fef49e7fa7e1997310d705b2a6158ff8dc1cdfeb_full.jpg'; /** * Fetches avatar + persona name from Steam's GetPlayerSummaries endpoint. * Always resolves to a usable avatarUrl (falls back to the default * question-mark image on any failure — including a missing STEAM_API_KEY) * rather than null, so callers don't need their own fallback logic. * * Cached for an hour via Next's extended fetch() — avatars rarely change, * and this keeps calls well within Steam's rate limits without needing a * separate DB cache table. */ export async function getSteamSummary(steamId64: string): Promise { const apiKey = process.env.STEAM_API_KEY; if (!apiKey) { return { avatarUrl: DEFAULT_AVATAR_URL, personaName: null }; } try { const res = await fetch( `https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${apiKey}&steamids=${steamId64}`, { next: { revalidate: 3600 } }, ); if (!res.ok) { return { avatarUrl: DEFAULT_AVATAR_URL, personaName: null }; } const data = await res.json(); const player = data?.response?.players?.[0]; return { avatarUrl: player?.avatarfull || DEFAULT_AVATAR_URL, personaName: player?.personaname ?? null, }; } catch { return { avatarUrl: DEFAULT_AVATAR_URL, personaName: null }; } } /** * Same as getSteamSummary, but for many players in one go — Steam's * GetPlayerSummaries accepts up to 100 comma-separated steamids per call, * so fetching a list of players (e.g. "who was online at this point in * time") is one HTTP call per 100 players rather than one per player. * Returns a map keyed by SteamID64; missing entries mean "use the default * avatar" at the call site. */ export async function getSteamSummariesBatch( steamId64List: string[], ): Promise> { const result = new Map(); const apiKey = process.env.STEAM_API_KEY; if (!apiKey || steamId64List.length === 0) { return result; } const chunks: string[][] = []; for (let i = 0; i < steamId64List.length; i += 100) { chunks.push(steamId64List.slice(i, i + 100)); } await Promise.all( chunks.map(async (chunk) => { try { const res = await fetch( `https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${apiKey}&steamids=${chunk.join(',')}`, { next: { revalidate: 3600 } }, ); if (!res.ok) return; const data = await res.json(); const players = data?.response?.players ?? []; for (const player of players) { result.set(player.steamid, { avatarUrl: player.avatarfull || DEFAULT_AVATAR_URL, personaName: player.personaname ?? null, }); } } catch { // this chunk's players just fall back to the default avatar downstream } }), ); return result; }