/** * YÖK Atlas'tan üniversite logolarını indirir. * * Kaynak: yokatlas.yok.gov.tr/api — SPA'nın kendi kullandığı uç noktalar. * Logo endpoint'i Referer olmadan 403 verdiği için hotlink yerine build * öncesi tek seferlik indirme yapılır; dosyalar public/uni-logo/.png. * * Çıktı olarak src/lib/uni-logolar.ts manifestini de üretir; bileşenler * logo var/yok kararını dosya sistemine bakmadan bu set üzerinden verir. * * Kullanım: npx tsx scripts/logo-indir.ts [--force] */ import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import path from "node:path"; import Database from "better-sqlite3"; import { turkishSlugify } from "../src/lib/slug"; const API = "https://yokatlas.yok.gov.tr/api"; const HEADERS = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", Referer: "https://yokatlas.yok.gov.tr/", }; const BEKLEME_MS = 250; const force = process.argv.includes("--force"); // katalog.ts/uniMapGetir ile aynı anahtar: "(İL)" eki atılmış adın slug'ı function uniSlug(ad: string): string { return turkishSlugify(ad.replace(/\s*\([^)]*\)\s*$/, "").trim()); } const bekle = (ms: number) => new Promise((r) => setTimeout(r, ms)); async function main() { const logoDir = path.join(process.cwd(), "public", "uni-logo"); mkdirSync(logoDir, { recursive: true }); // 1) Sitedeki üniversiteler (DB'deki ham adlardan slug kümesi) const db = new Database(path.join(process.cwd(), "data", "yokatlas.db"), { readonly: true, }); const rows = db .prepare("SELECT DISTINCT universite FROM programs") .all() as { universite: string }[]; db.close(); const siteSluglari = new Set( rows.map((r) => uniSlug(r.universite)).filter(Boolean), ); // 2) YÖK Atlas üniversite listesi const res = await fetch(`${API}/tercih-kilavuz/universiteler`, { headers: HEADERS, }); if (!res.ok) throw new Error(`üniversite listesi: HTTP ${res.status}`); const apiListe = (await res.json()) as { universiteAdi: string; universiteId: number; }[]; console.log( `Site: ${siteSluglari.size} üniversite, API: ${apiListe.length} üniversite`, ); // 3) Slug eşlemesi + indirme const indirilen: string[] = []; const atlanan: string[] = []; const hatali: { slug: string; durum: string }[] = []; const apiSluglari = new Set(); for (const u of apiListe) { const slug = uniSlug(u.universiteAdi); if (!slug) continue; apiSluglari.add(slug); if (!siteSluglari.has(slug)) { atlanan.push(`${u.universiteAdi} (sitede yok)`); continue; } const dosya = path.join(logoDir, `${slug}.png`); if (!force && existsSync(dosya)) { indirilen.push(slug); continue; } await bekle(BEKLEME_MS); try { const logoRes = await fetch( `${API}/universite-logo?universiteId=${u.universiteId}`, { headers: HEADERS }, ); if (!logoRes.ok) { hatali.push({ slug, durum: `HTTP ${logoRes.status}` }); continue; } const buf = Buffer.from(await logoRes.arrayBuffer()); // PNG imzası kontrolü — WAF hata sayfası vb. çöpü diske yazma if (buf.length < 8 || buf.readUInt32BE(0) !== 0x89504e47) { hatali.push({ slug, durum: "PNG değil" }); continue; } writeFileSync(dosya, buf); indirilen.push(slug); console.log(` ✓ ${slug} (${(buf.length / 1024).toFixed(0)} KB)`); } catch (e) { hatali.push({ slug, durum: String(e) }); } } // 4) Manifest: elle eklenen logolar da (API dışı) sete girsin const { readdirSync } = await import("node:fs"); const diskteki = readdirSync(logoDir) .filter((f) => f.endsWith(".png")) .map((f) => f.slice(0, -4)) .sort(); const manifest = `// Bu dosya scripts/logo-indir.ts tarafından üretilir — elle düzenleme. // Logo dosyaları: public/uni-logo/.png (kaynak: YÖK Atlas) export const UNI_LOGO_SLUGLARI: ReadonlySet = new Set([ ${diskteki.map((s) => ` "${s}",`).join("\n")} ]); export function uniLogoYolu(slug: string): string | null { return UNI_LOGO_SLUGLARI.has(slug) ? \`/uni-logo/\${slug}.png\` : null; } `; writeFileSync(path.join(process.cwd(), "src", "lib", "uni-logolar.ts"), manifest); // 5) Rapor const eksik = [...siteSluglari].filter((s) => !diskteki.includes(s)).sort(); console.log(`\nİndirilen/mevcut: ${indirilen.length}`); if (hatali.length) { console.log(`Hatalı (${hatali.length}):`); for (const h of hatali) console.log(` ✗ ${h.slug}: ${h.durum}`); } if (atlanan.length) { console.log(`API'de olup sitede olmayan (${atlanan.length}):`); for (const a of atlanan) console.log(` - ${a}`); } if (eksik.length) { console.log(`Logosu eksik kalan site üniversiteleri (${eksik.length}):`); for (const e of eksik) console.log(` ! ${e}`); } console.log(`Manifest: ${diskteki.length} logo → src/lib/uni-logolar.ts`); } main().catch((e) => { console.error(e); process.exit(1); });