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}`,
+ })),
};
};