export interface GiteaRepoInfo { commits: number; releases: number; language: string; updatedAt: string; size: number; defaultBranch: string; topics: string[]; } export interface GiteaConfig { domain: string; owner: string; repo: string; } export function parseGiteaUrl(url: string): GiteaConfig | null { try { const urlObj = new URL(url); const pathParts = urlObj.pathname.split("/").filter((p) => p); if (pathParts.length >= 2) { return { domain: urlObj.origin, owner: pathParts[0], repo: pathParts[1], }; } } catch (e) { // Invalid URL } return null; } export async function fetchGiteaRepoInfo( config: GiteaConfig, ): Promise { try { const apiUrl = `${config.domain}/api/v1/repos/${config.owner}/${config.repo}`; const response = await fetch(apiUrl, { headers: { Accept: "application/json", }, signal: AbortSignal.timeout(5000), }); if (!response.ok) { return null; } const data = await response.json(); let commitsCount = 0; try { const commitsUrl = `${config.domain}/api/v1/repos/${config.owner}/${config.repo}/commits?limit=1&stat=false`; const commitsResponse = await fetch(commitsUrl, { headers: { Accept: "application/json", }, signal: AbortSignal.timeout(5000), }); if (commitsResponse.ok) { const totalCount = commitsResponse.headers.get("X-Total-Count"); commitsCount = totalCount ? parseInt(totalCount, 10) : 0; } } catch (error) { // Ignore } let releasesCount = 0; try { const releasesUrl = `${config.domain}/api/v1/repos/${config.owner}/${config.repo}/releases`; const releasesResponse = await fetch(releasesUrl, { headers: { Accept: "application/json", }, signal: AbortSignal.timeout(5000), }); if (releasesResponse.ok) { const releases = await releasesResponse.json(); releasesCount = Array.isArray(releases) ? releases.length : 0; } } catch (error) { // Ignore } return { commits: commitsCount, releases: releasesCount, language: data.language || "Unknown", updatedAt: data.updated_at || data.pushed_at || "", size: data.size || 0, defaultBranch: data.default_branch || "main", topics: Array.isArray(data.topics) ? data.topics : [], }; } catch (error) { return null; } } export async function fetchGiteaInfoFromUrl( url: string, ): Promise { const config = parseGiteaUrl(url); if (!config) { return null; } return fetchGiteaRepoInfo(config); } export function formatRelativeTime(dateString: string): string { if (!dateString) return "Unknown"; const date = new Date(dateString); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffMinutes = Math.floor(diffMs / (1000 * 60)); if (diffMinutes < 60) { return `${diffMinutes} minute${diffMinutes !== 1 ? "s" : ""} ago`; } else if (diffHours < 24) { return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`; } else if (diffDays < 30) { return `${diffDays} day${diffDays !== 1 ? "s" : ""} ago`; } else if (diffDays < 365) { const months = Math.floor(diffDays / 30); return `${months} month${months !== 1 ? "s" : ""} ago`; } else { const years = Math.floor(diffDays / 365); return `${years} year${years !== 1 ? "s" : ""} ago`; } } export function formatRepoSize(sizeKb: number): string { if (sizeKb < 1024) { return `${sizeKb} KB`; } else if (sizeKb < 1024 * 1024) { return `${(sizeKb / 1024).toFixed(1)} MB`; } else { return `${(sizeKb / (1024 * 1024)).toFixed(1)} GB`; } }