// Gitea 언어 통계 조회 — 프로젝트의 repo_url 로 메인 언어를 자동 판별. // 공개 레포는 토큰 없이도 조회되고, 비공개 레포는 GITEA_TOKEN 이 있어야 한다. // 네트워크 실패/타임아웃/비-Gitea URL 이면 빈 문자열을 반환해 호출부가 폴백하도록 한다. import { config } from "./config.js"; // repo_url → { base, owner, repo } 파싱. URL 형태가 아니거나 owner/repo 가 없으면 null. // 예) https://gitea.bokdev.in/hong/my-app(.git) → { base, owner: hong, repo: my-app } export function parseRepo(repoUrl) { if (!repoUrl) return null; let u; try { u = new URL(repoUrl); } catch { return null; } const parts = u.pathname.replace(/\.git$/, "").split("/").filter(Boolean); if (parts.length < 2) return null; return { base: `${u.protocol}//${u.host}`, owner: parts[0], repo: parts[1] }; } // 레포의 메인 언어(바이트 최다) 1개를 반환. 실패/없음이면 "". export async function fetchMainLang(repoUrl) { const r = parseRepo(repoUrl); if (!r) return ""; const url = `${r.base}/api/v1/repos/${encodeURIComponent(r.owner)}/${encodeURIComponent(r.repo)}/languages`; const headers = { Accept: "application/json" }; if (config.giteaToken) headers.Authorization = `token ${config.giteaToken}`; // 등록 응답이 Gitea 응답을 기다리다 멈추지 않도록 3초 타임아웃. const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), 3000); try { const res = await fetch(url, { headers, signal: ctrl.signal }); if (!res.ok) return ""; const data = await res.json(); // { "JavaScript": 12345, "CSS": 678, ... } let top = ""; let max = -1; for (const [lang, bytes] of Object.entries(data || {})) { if (typeof bytes === "number" && bytes > max) { max = bytes; top = lang; } } return top; } catch { return ""; } finally { clearTimeout(timer); } }