import { warnDev } from "@/lib/log"; export function discordAppIconUrl( applicationId: string, iconHash: string | null, size = 128, ): string | null { if (!iconHash) { return null; } const extension = iconHash.startsWith("a_") ? "gif" : "png"; return `https://cdn.discordapp.com/app-icons/${applicationId}/${iconHash}.${extension}?size=${size}`; } export function discordGameUrl(gameId: string): string { return `https://discord.com/games/${gameId}`; } export function discordAppCoverUrl( applicationId: string, coverHash: string | null, size = 512, ): string | null { if (!coverHash) { return null; } return `https://cdn.discordapp.com/app-icons/${applicationId}/${coverHash}.png?size=${size}`; } export function steamdbAppUrl(steamId: string): string { return `https://steamdb.info/app/${steamId}/`; } export interface DiscordAppInfo { id: string; name: string; icon: string | null; iconUrl: string | null; coverImage: string | null; coverUrl: string | null; gameId: string; gameUrl: string; } interface DiscordRpcResponse { id?: string; name?: string; icon?: string | null; cover_image?: string | null; linked_games?: Array<{ id: string }> | null; } export async function getDiscordAppInfo(applicationId: string): Promise { if (!/^\d+$/.test(applicationId)) { return null; } let response: Response; try { response = await fetch(`https://discord.com/api/v10/applications/${applicationId}/rpc`, { headers: { "User-Agent": "fristy.net (personal site)" }, next: { revalidate: 86_400 }, }); } catch (error) { throw new Error(`discord rpc unreachable: ${applicationId}`, { cause: error }); } if (!response.ok) { if (response.status === 404) { return null; } if (response.status >= 500 || response.status === 429) { throw new Error(`discord rpc upstream failed: ${applicationId} -> ${response.status}`, { cause: new Error(`HTTP ${response.status}`), }); } warnDev(`discord app failed: ${response.status}`); return null; } let json: DiscordRpcResponse; try { json = (await response.json()) as DiscordRpcResponse; } catch (error) { throw new Error(`discord rpc malformed response: ${applicationId}`, { cause: error }); } const gameId = json.linked_games?.[0]?.id ?? json.id ?? applicationId; return { id: json.id ?? applicationId, name: json.name ?? "", icon: json.icon ?? null, iconUrl: discordAppIconUrl(applicationId, json.icon ?? null), coverImage: json.cover_image ?? null, coverUrl: discordAppCoverUrl(applicationId, json.cover_image ?? null), gameId, gameUrl: discordGameUrl(gameId), }; }