Files
fristy ff18fc353d
CI / Code Health (pull_request) Successful in 22s
CI / Lint, Test & Build (pull_request) Successful in 46s
CI / Code Health (push) Successful in 25s
CI / Lint, Test & Build (push) Successful in 54s
Deploy / Build & Push Image (push) Successful in 56s
Deploy / Trigger Deploy (push) Successful in 1s
Domain degisikligi: fristy.net -> fristy.dev
2026-10-15 21:48:52 +03:00

96 lines
2.9 KiB
TypeScript

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<DiscordAppInfo | null> {
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.dev (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),
};
}