| import type { GitHubRepoData } from "@/types"; |
|
|
| export function parseGitHubUrl(url: string): { owner: string; repo: string } | null { |
| const trimmed = url.trim(); |
| const patterns = [ |
| /github\.com\/([^/\s]+)\/([^/\s#?]+)/i, |
| /^([^/\s]+)\/([^/\s]+)$/, |
| ]; |
|
|
| for (const pattern of patterns) { |
| const match = trimmed.match(pattern); |
| if (match) { |
| return { |
| owner: match[1].replace(/\.git$/, ""), |
| repo: match[2].replace(/\.git$/, ""), |
| }; |
| } |
| } |
| return null; |
| } |
|
|
| async function githubFetch(path: string): Promise<Response> { |
| const headers: Record<string, string> = { |
| Accept: "application/vnd.github+json", |
| "User-Agent": "caption-studio", |
| }; |
| |
| const token = process.env.GITHUB_TOKEN?.trim(); |
| if (token && token.length > 20) { |
| headers.Authorization = `Bearer ${token}`; |
| } |
| return fetch(`https://api.github.com${path}`, { headers }); |
| } |
|
|
| export async function fetchRepoData(url: string): Promise<GitHubRepoData> { |
| const parsed = parseGitHubUrl(url); |
| if (!parsed) throw new Error("Invalid GitHub repo URL"); |
|
|
| const { owner, repo } = parsed; |
| const repoRes = await githubFetch(`/repos/${owner}/${repo}`); |
| if (!repoRes.ok) { |
| throw new Error( |
| `GitHub repo fetch failed (${repoRes.status}): ${owner}/${repo}`, |
| ); |
| } |
| const repoData = await repoRes.json(); |
|
|
| let readme = ""; |
| const readmeRes = await githubFetch(`/repos/${owner}/${repo}/readme`); |
| if (readmeRes.ok) { |
| readme = await readmeRes.text(); |
| if (readme.length > 4000) readme = readme.slice(0, 4000) + "..."; |
| } |
|
|
| return { |
| owner, |
| repo, |
| fullName: repoData.full_name as string, |
| htmlUrl: repoData.html_url as string, |
| description: (repoData.description as string) || "", |
| stars: (repoData.stargazers_count as number) || 0, |
| language: (repoData.language as string) || "Unknown", |
| topics: (repoData.topics as string[]) || [], |
| readme, |
| }; |
| } |
|
|