desloppify: code health overhaul plus abuse hardening (140 green, strict 87.2)
Honest baseline after removing 45 gaming suppressions. Structural: lib/game plus root hooks extraction, CORS origin echo, DEV_ORIGINS layering. Fixes: error-cause binding, discord 5xx split, null-cache guard, co-located tests, rejecting-fetch coverage, naming and typing cleanup, socket controller, cache bound, cascade helpers, latch resets. Hardening: per-IP rate limit on API routes, deterministic-miss UX, retry cooldown plus cap. Suite 140 green, build plus lint clean.
This commit is contained in:
78 files changed
+4908
-1430
No files matched your search
@@ -0,0 +1,287 @@
|
||||
import { getDiscordAppInfo } from "@/lib/game/discord-app";
|
||||
import { getDetectables, steamIdFromEntry } from "@/lib/game/detectables";
|
||||
import { getSteamDetails, getSteamReviewSummary } from "@/lib/game/steam";
|
||||
import { getIgdbGame, igdbImage } from "@/lib/game/igdb";
|
||||
import { getItadId, itadFetch } from "@/lib/game/itad";
|
||||
import { toIsoDate } from "@/lib/game/game-format";
|
||||
import type {
|
||||
GameExternalLink,
|
||||
GamePricePoint,
|
||||
GameProfile,
|
||||
IgdbGame,
|
||||
ItadGameInfo,
|
||||
ItadOverviewPrice,
|
||||
ItadReview,
|
||||
SteamAppDetails,
|
||||
} from "@/lib/game/game-profile-types";
|
||||
import type { DetectableEntry } from "@/lib/game/game-profile-types";
|
||||
|
||||
const DISTRIBUTOR_PLATFORM: Partial<Record<string, string>> = {
|
||||
xbox: "Xbox",
|
||||
xbox_title: "Xbox",
|
||||
xbox_game_pass: "Xbox",
|
||||
playstation: "PlayStation",
|
||||
nintendo: "Switch",
|
||||
};
|
||||
|
||||
const IGDB_WEBSITE_LABELS: Record<number, string> = {
|
||||
1: "Website",
|
||||
4: "Facebook",
|
||||
5: "X",
|
||||
6: "Twitch",
|
||||
8: "Instagram",
|
||||
9: "YouTube",
|
||||
13: "Steam",
|
||||
14: "Reddit",
|
||||
16: "Epic",
|
||||
17: "GOG",
|
||||
18: "Discord",
|
||||
};
|
||||
|
||||
type SteamData = SteamAppDetails["data"];
|
||||
|
||||
export function mergePeople(
|
||||
igdb: IgdbGame | null,
|
||||
steam: SteamData | null,
|
||||
itadInfo: ItadGameInfo | null,
|
||||
): { developers: string[]; publishers: string[] } {
|
||||
const companies = igdb?.involved_companies ?? [];
|
||||
if (companies.length === 0) {
|
||||
const steamDevs = steam?.developers?.length
|
||||
? steam.developers
|
||||
: itadInfo?.developers?.map((d) => d.name).filter((n): n is string => !!n) || [];
|
||||
const steamPubs = steam?.publishers?.length
|
||||
? steam.publishers
|
||||
: itadInfo?.publishers?.map((d) => d.name).filter((n): n is string => !!n) || [];
|
||||
return { developers: steamDevs, publishers: steamPubs };
|
||||
}
|
||||
const names = (developer: boolean): string[] => [
|
||||
...new Set(
|
||||
companies
|
||||
.filter((c) => (developer ? c.developer : c.publisher))
|
||||
.map((c) => c.company?.name)
|
||||
.filter((n): n is string => !!n),
|
||||
),
|
||||
];
|
||||
return { developers: names(true), publishers: names(false) };
|
||||
}
|
||||
|
||||
export function mergePlatforms(
|
||||
igdb: IgdbGame | null,
|
||||
steam: SteamData | null,
|
||||
entry: DetectableEntry | undefined,
|
||||
): string[] {
|
||||
const platforms = new Set<string>();
|
||||
for (const p of igdb?.platforms ?? []) {
|
||||
const raw = (p.name ?? "").trim();
|
||||
if (!raw) {
|
||||
continue;
|
||||
}
|
||||
if (raw === "PC (Microsoft Windows)") {
|
||||
platforms.add("Windows");
|
||||
} else if (raw === "Mac") {
|
||||
platforms.add("MacOS");
|
||||
} else {
|
||||
platforms.add(raw);
|
||||
}
|
||||
}
|
||||
if (steam?.platforms) {
|
||||
if (steam.platforms.windows) {
|
||||
platforms.add("Windows");
|
||||
}
|
||||
if (steam.platforms.mac) {
|
||||
platforms.add("MacOS");
|
||||
}
|
||||
if (steam.platforms.linux) {
|
||||
platforms.add("Linux");
|
||||
}
|
||||
}
|
||||
for (const sku of entry?.third_party_skus ?? []) {
|
||||
const mapped = DISTRIBUTOR_PLATFORM[sku.distributor];
|
||||
if (mapped) {
|
||||
platforms.add(mapped);
|
||||
}
|
||||
}
|
||||
return [...platforms];
|
||||
}
|
||||
|
||||
export function buildLinks(igdb: IgdbGame | null): GameExternalLink[] {
|
||||
const links: GameExternalLink[] = [];
|
||||
for (const site of igdb?.websites ?? []) {
|
||||
if (!site.url) {
|
||||
continue;
|
||||
}
|
||||
const url = site.url.startsWith("http") ? site.url : `https://${site.url}`;
|
||||
let parsed: URL | null = null;
|
||||
try {
|
||||
parsed = new URL(url);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (parsed.protocol !== "https:") {
|
||||
continue;
|
||||
}
|
||||
let label = site.category ? (IGDB_WEBSITE_LABELS[site.category] ?? null) : null;
|
||||
if (!label) {
|
||||
label = parsed.hostname.replace(/^www\./, "");
|
||||
}
|
||||
if (!links.some((l) => l.url === url)) {
|
||||
links.push({ label, url });
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
export function selectScreenshots(igdb: IgdbGame | null, steam: SteamData | null): string[] {
|
||||
return [
|
||||
...(igdb?.screenshots ?? [])
|
||||
.map((s) => igdbImage(s.url, "t_screenshot_big"))
|
||||
.filter((u): u is string => !!u),
|
||||
...(steam?.screenshots ?? []).map((s) => s.path_full).filter((u): u is string => !!u),
|
||||
].slice(0, 8);
|
||||
}
|
||||
|
||||
export function buildGenres(igdb: IgdbGame | null, itadInfo: ItadGameInfo | null): string[] {
|
||||
return [
|
||||
...(igdb?.genres ?? []).map((g) => g.name).filter((n): n is string => !!n),
|
||||
...(igdb?.genres?.some((g) => !!g.name) ? [] : (itadInfo?.tags ?? []).slice(0, 5)),
|
||||
].slice(0, 6);
|
||||
}
|
||||
|
||||
export function selectDescription(
|
||||
lang: string,
|
||||
igdb: IgdbGame | null,
|
||||
steam: SteamData | null,
|
||||
): string | null {
|
||||
return lang === "en"
|
||||
? (igdb?.summary ?? steam?.short_description ?? null)
|
||||
: (steam?.short_description ?? igdb?.summary ?? null);
|
||||
}
|
||||
|
||||
export function selectArtwork(
|
||||
igdb: IgdbGame | null,
|
||||
steam: SteamData | null,
|
||||
itadInfo: ItadGameInfo | null,
|
||||
): string | null {
|
||||
return (
|
||||
igdbImage(igdb?.cover?.url, "t_cover_big") ??
|
||||
steam?.header_image ??
|
||||
itadInfo?.assets?.banner600 ??
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
export function toPricePoint(p: ItadOverviewPrice["current"]): GamePricePoint | null {
|
||||
if (!p?.price || typeof p.price.amount !== "number") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
price: p.price.amount,
|
||||
regular: p.regular?.amount ?? p.price.amount,
|
||||
currency: p.price.currency ?? "",
|
||||
shop: p.shop?.name ?? "",
|
||||
url: p.url?.startsWith("https://") ? p.url : "",
|
||||
};
|
||||
}
|
||||
|
||||
export function mapItadBlock(
|
||||
itadInfo: ItadGameInfo | null,
|
||||
priceEntry: { current?: ItadOverviewPrice["current"]; lowest?: ItadOverviewPrice["current"] } | undefined,
|
||||
): NonNullable<GameProfile["itad"]> | null {
|
||||
if (!process.env.ITAD_API_KEY || (!itadInfo && !priceEntry)) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
prices: toPricePoint(priceEntry?.current),
|
||||
historyLow: toPricePoint(priceEntry?.lowest),
|
||||
pageUrl: itadInfo?.urls?.game ?? null,
|
||||
playerCount: itadInfo?.players?.recent ?? null,
|
||||
reviews: (itadInfo?.reviews ?? [])
|
||||
.filter(
|
||||
(r): r is ItadReview & { source: string; url: string } =>
|
||||
!!r.source && !!r.url,
|
||||
)
|
||||
.map((r) => ({
|
||||
score: r.score ?? null,
|
||||
source: r.source,
|
||||
count: r.count ?? null,
|
||||
url: r.url,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getGameProfile(
|
||||
applicationId: string,
|
||||
lang = "en",
|
||||
country = "TR",
|
||||
): Promise<GameProfile | null> {
|
||||
if (!/^\d+$/.test(applicationId)) {
|
||||
return null;
|
||||
}
|
||||
const appInfo = await getDiscordAppInfo(applicationId);
|
||||
if (!appInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const detectables = await getDetectables();
|
||||
const entry = detectables.find((e) => e.id === appInfo.gameId || e.id === applicationId);
|
||||
const steamId = steamIdFromEntry(entry);
|
||||
|
||||
const [steam, steamReviews, igdb] = await Promise.all([
|
||||
steamId ? getSteamDetails(steamId, lang, country) : Promise.resolve(null),
|
||||
steamId ? getSteamReviewSummary(steamId) : Promise.resolve(null),
|
||||
getIgdbGame(appInfo.name || entry?.name || "", steamId),
|
||||
]);
|
||||
|
||||
const itadId = process.env.ITAD_API_KEY
|
||||
? await getItadId(igdb?.name ?? steam?.name ?? appInfo.name)
|
||||
: null;
|
||||
const [itadInfo, itadOverview] = itadId
|
||||
? await Promise.all([
|
||||
itadFetch<ItadGameInfo>(`/games/info/v2?id=${itadId}`),
|
||||
itadFetch<{ prices?: ItadOverviewPrice[] }>(`/games/overview/v2?country=${country}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify([itadId]),
|
||||
}),
|
||||
])
|
||||
: [null, null];
|
||||
|
||||
const { developers, publishers } = mergePeople(igdb, steam, itadInfo);
|
||||
const platforms = mergePlatforms(igdb, steam, entry);
|
||||
const links = buildLinks(igdb);
|
||||
const screenshots = selectScreenshots(igdb, steam);
|
||||
|
||||
const priceEntry = itadOverview?.prices?.[0];
|
||||
|
||||
return {
|
||||
applicationId,
|
||||
gameId: appInfo.gameId,
|
||||
gameUrl: appInfo.gameUrl,
|
||||
name: igdb?.name ?? steam?.name ?? appInfo.name,
|
||||
genres: buildGenres(igdb, itadInfo),
|
||||
description: selectDescription(lang, igdb, steam),
|
||||
developers,
|
||||
publishers,
|
||||
releaseDate:
|
||||
toIsoDate(igdb?.first_release_date) ??
|
||||
itadInfo?.releaseDate ??
|
||||
steam?.release_date?.date ??
|
||||
null,
|
||||
platforms,
|
||||
links,
|
||||
coverUrl: igdbImage(igdb?.cover?.url, "t_cover_big"),
|
||||
artworkUrl: selectArtwork(igdb, steam, itadInfo),
|
||||
screenshots,
|
||||
steamId,
|
||||
steamUrl: steamId ? `https://store.steampowered.com/app/${steamId}/` : null,
|
||||
igdbUrl: igdb?.slug ? `https://www.igdb.com/games/${igdb.slug}` : null,
|
||||
steamReviews,
|
||||
itad: mapItadBlock(itadInfo, priceEntry),
|
||||
sources: {
|
||||
igdb: !!igdb,
|
||||
steam: !!steam,
|
||||
itad: !!(itadInfo || priceEntry),
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user