diff --git a/src/lib/api.ts b/src/lib/api.ts index b025142..367a20f 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -76,3 +76,51 @@ const RANDOM_KEYWORDS = ["world", "mod", "skin", "map", "adventure", "survival", export function getRandomKeyword(): string { return RANDOM_KEYWORDS[Math.floor(Math.random() * RANDOM_KEYWORDS.length)]; } + +// ─── CurseForge API ─── + +const SUPABASE_URL_CF = import.meta.env.VITE_SUPABASE_URL; + +let lastCFSearchTime = 0; + +async function callFetchCurseForge(params: Record) { + const now = Date.now(); + if (now - lastCFSearchTime < SEARCH_COOLDOWN) { + await new Promise((r) => setTimeout(r, SEARCH_COOLDOWN - (now - lastCFSearchTime))); + } + lastCFSearchTime = Date.now(); + + const searchParams = new URLSearchParams(params); + const url = `${SUPABASE_URL_CF}/functions/v1/fetch-curseforge?${searchParams.toString()}`; + const response = await fetch(url, { + headers: { + Authorization: `Bearer ${import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY}`, + }, + }); + + if (response.status === 429) { + throw new Error("تم تجاوز الحد المسموح، حاول مرة أخرى بعد قليل"); + } + if (!response.ok) { + throw new Error(`CurseForge API error: ${response.status}`); + } + return response.json(); +} + +export async function searchCurseForge(query: string, offset = 0, limit = 20, classId = "") { + const cleanQuery = query.slice(0, 200).replace(/[<>{}]/g, "").trim(); + if (!cleanQuery) return { data: [], pagination: { totalCount: 0 } }; + const params: Record = { action: "search", query: cleanQuery, offset: String(offset), limit: String(limit) }; + if (classId) params.classId = classId; + return callFetchCurseForge(params); +} + +export async function getCurseForgeMod(id: string) { + if (!/^\d+$/.test(id)) throw new Error("Invalid mod id"); + return callFetchCurseForge({ action: "mod", id }); +} + +export async function getCurseForgeFiles(id: string) { + if (!/^\d+$/.test(id)) throw new Error("Invalid mod id"); + return callFetchCurseForge({ action: "files", id }); +}