diff --git a/docs/en/programming.md b/docs/en/programming.md index 59651e7cf2..3a653b03a8 100644 --- a/docs/en/programming.md +++ b/docs/en/programming.md @@ -137,7 +137,7 @@ GitHub provides some official RSS feeds: ### Trending - + ### Topics diff --git a/docs/programming.md b/docs/programming.md index 5c5a95b6f4..83a488819a 100644 --- a/docs/programming.md +++ b/docs/programming.md @@ -237,7 +237,7 @@ GitHub 官方也提供了一些 RSS: ### Trending - + ### Topics diff --git a/lib/v2/github/trending.js b/lib/v2/github/trending.js index ca2ba42c08..62f3a5bef6 100644 --- a/lib/v2/github/trending.js +++ b/lib/v2/github/trending.js @@ -1,59 +1,91 @@ +const config = require('@/config').value; const got = require('@/utils/got'); const { art } = require('@/utils/render'); const cheerio = require('cheerio'); const path = require('path'); module.exports = async (ctx) => { + if (!config.github || !config.github.access_token) { + throw 'GitHub trending RSS is disabled due to the lack of relevant config'; + } const since = ctx.params.since; const language = ctx.params.language === 'any' ? '' : ctx.params.language; const spoken_language = ctx.params.spoken_language ?? ''; - const url = `https://github.com/trending/${encodeURIComponent(language)}?since=${since}&spoken_language_code=${spoken_language}`; - const response = await got({ + const trendingUrl = `https://github.com/trending/${encodeURIComponent(language)}?since=${since}&spoken_language_code=${spoken_language}`; + const { data: trendingPage } = await got({ method: 'get', - url, + url: trendingUrl, headers: { - Referer: url, + Referer: trendingUrl, + }, + }); + const $ = cheerio.load(trendingPage); + + const articles = $('article'); + const trendingRepos = articles + .map((_, item) => { + const [owner, name] = $(item).find('h1').text().split('/'); + return { + name: name.trim(), + owner: owner.trim(), + }; + }) + .get(); + + const { data: repoData } = await got({ + method: 'post', + url: 'https://api.github.com/graphql', + headers: { + Authorization: `bearer ${config.github.access_token}`, + }, + json: { + query: ` + query { + ${trendingRepos + .map( + (repo, index) => ` + _${index}: repository(owner: "${repo.owner}", name: "${repo.name}") { + ...RepositoryFragment + } + ` + ) + .join('\n')} + } + + fragment RepositoryFragment on Repository { + description + forkCount + nameWithOwner + openGraphImageUrl + primaryLanguage { + name + } + stargazerCount + } + `, }, }); - const data = response.data; - - const $ = cheerio.load(data); - const list = $('article'); - - const items = await Promise.all( - list.map((_, item) => { - item = $(item); - const endpoint = item.find('h1 a').attr('href'); - const link = `https://github.com${endpoint}`; - return ctx.cache.tryGet(`github:trending:${endpoint}`, async () => { - const response = await got(link); - - const $ = cheerio.load(response.data); - const cover = $('meta[property="og:image"]'); - - const single = { - title: item.find('h1').text(), - author: item.find('h1').text().split('/')[0].trim(), - description: art(path.join(__dirname, 'templates/trending-description.art'), { - cover: cover.attr('content'), - desc: item.find('p').text(), - lang: item.find('span[itemprop="programmingLanguage"]').text() || 'Unknown', - stars: item.find('.Link--muted').eq(0).text().trim(), - forks: item.find('.Link--muted').eq(1).text().trim(), - }), - link, - }; - - return single; - }); - }) - ); + const repos = Object.values(repoData.data).map((repo) => { + const found = trendingRepos.find((r) => `${r.owner}/${r.name}` === repo.nameWithOwner); + return { ...found, ...repo }; + }); ctx.state.data = { title: $('title').text(), - link: url, - item: items, + link: trendingUrl, + item: repos.map((r) => ({ + title: r.nameWithOwner, + author: r.owner, + description: art(path.join(__dirname, 'templates/trending-description.art'), { + cover: r.openGraphImageUrl, + desc: r.description, + forks: r.forkCount, + lang: r.primaryLanguage?.name || 'Unknown', + stars: r.stargazerCount, + }), + link: `https://github.com/${r.nameWithOwner}`, + })), }; };