Skip to main content
Glama
trending.ts4.69 kB
/** * GitHub Trending API - Cloudflare Pages Function * GET /api/trending?language=python&since=daily */ interface Env { TRENDING_CACHE?: KVNamespace; } interface TrendingRepo { rank: number; username: string; reponame: string; url: string; description: string; language: string; stars: number; forks: number; starsToday: number; } const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', }; export const onRequestOptions: PagesFunction = async () => { return new Response(null, { headers: corsHeaders }); }; export const onRequestGet: PagesFunction<Env> = async (context) => { const url = new URL(context.request.url); const language = url.searchParams.get('language') || ''; const since = (url.searchParams.get('since') || 'daily') as 'daily' | 'weekly' | 'monthly'; if (!['daily', 'weekly', 'monthly'].includes(since)) { return jsonResponse({ error: 'Invalid since parameter. Use: daily, weekly, monthly' }, 400); } const cacheKey = `trending:${language || 'all'}:${since}`; // 尝试从缓存读取 if (context.env.TRENDING_CACHE) { const cached = await context.env.TRENDING_CACHE.get(cacheKey); if (cached) { return jsonResponse({ data: JSON.parse(cached), cached: true, language: language || 'all', since, }); } } // 获取新数据 try { const repos = await fetchTrendingRepos(language, since); // 写入缓存(1小时过期) if (context.env.TRENDING_CACHE) { await context.env.TRENDING_CACHE.put(cacheKey, JSON.stringify(repos), { expirationTtl: 3600 }); } return jsonResponse({ data: repos, cached: false, language: language || 'all', since, }); } catch (error) { console.error('Error fetching trending:', error); return jsonResponse({ error: 'Failed to fetch trending repos' }, 500); } }; async function fetchTrendingRepos( language: string = '', since: 'daily' | 'weekly' | 'monthly' = 'daily' ): Promise<TrendingRepo[]> { let url = 'https://github.com/trending'; if (language) { url += `/${encodeURIComponent(language)}`; } url += `?since=${since}`; const response = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (compatible; GitHubTrendingBot/1.0)', 'Accept': 'text/html,application/xhtml+xml', 'Accept-Language': 'en-US,en;q=0.9', }, }); if (!response.ok) { throw new Error(`Failed to fetch trending: ${response.status}`); } const html = await response.text(); return parseTrendingHtml(html); } function parseTrendingHtml(html: string): TrendingRepo[] { const repos: TrendingRepo[] = []; const articleRegex = /<article class="Box-row"[^>]*>([\s\S]*?)<\/article>/g; let match; let rank = 0; while ((match = articleRegex.exec(html)) !== null) { rank++; const articleHtml = match[1]; try { const repo = parseRepoArticle(articleHtml, rank); if (repo) repos.push(repo); } catch (e) { console.error(`Failed to parse repo at rank ${rank}:`, e); } } return repos; } function parseRepoArticle(html: string, rank: number): TrendingRepo | null { const repoLinkMatch = html.match(/href="\/([^/]+)\/([^/"]+)"/); if (!repoLinkMatch) return null; const username = repoLinkMatch[1]; const reponame = repoLinkMatch[2]; const descMatch = html.match(/<p class="[^"]*col-9[^"]*"[^>]*>([\s\S]*?)<\/p>/); const description = descMatch ? descMatch[1].trim().replace(/<[^>]+>/g, '').trim() : ''; const langMatch = html.match(/itemprop="programmingLanguage">([^<]+)</); const language = langMatch ? langMatch[1].trim() : ''; const starsMatch = html.match(/href="[^"]*stargazers"[^>]*>[\s\S]*?([\d,]+)/i); const stars = starsMatch ? parseNumber(starsMatch[1]) : 0; const forksMatch = html.match(/href="[^"]*forks"[^>]*>[\s\S]*?([\d,]+)/i); const forks = forksMatch ? parseNumber(forksMatch[1]) : 0; const starsTodayMatch = html.match(/([\d,]+)\s*stars?\s*(today|this week|this month)/i); const starsToday = starsTodayMatch ? parseNumber(starsTodayMatch[1]) : 0; return { rank, username, reponame, url: `https://github.com/${username}/${reponame}`, description, language, stars, forks, starsToday, }; } function parseNumber(str: string): number { return parseInt(str.replace(/,/g, ''), 10) || 0; } function jsonResponse(data: unknown, status = 200): Response { return new Response(JSON.stringify(data, null, 2), { status, headers: { 'Content-Type': 'application/json', ...corsHeaders }, }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Xiaozhang22/cloudflare_mcp1'

If you have feedback or need assistance with the MCP directory API, please join our Discord server