fix(route): use GraphQL query in GitHub trending (#11188)

* fix(route): use GraphQL query in GitHub trending

Uses GraphQL to get desired repository data instead of attempting to
query it from the repository page.

* docs: add `selfhost` attribute to GitHub trending
This commit is contained in:
James Chen-Smith
2022-11-01 10:45:51 -05:00
committed by GitHub
parent 0397ca9065
commit e1fd9ca1e3
3 changed files with 73 additions and 41 deletions

View File

@@ -137,7 +137,7 @@ GitHub provides some official RSS feeds:
### Trending ### Trending
<RouteEn author="DIYgod" path="/github/trending/:since/:language/:spoken_language?" example="/github/trending/daily/javascript/en" :paramsDesc="['time frame, available in [Trending page](https://github.com/trending/javascript?since=monthly) \'s URL, possible values are: `daily`, `weekly` or `monthly`', 'the feed language, available in [Trending page](https://github.com/trending/javascript?since=monthly) \'s URL, don\'t filter option is `any`', 'natural language, available in [Trending page](https://github.com/trending/javascript?since=monthly) \'s URL']" radar="1" rssbud="1"/> <RouteEn author="DIYgod" path="/github/trending/:since/:language/:spoken_language?" example="/github/trending/daily/javascript/en" :paramsDesc="['time frame, available in [Trending page](https://github.com/trending/javascript?since=monthly) \'s URL, possible values are: `daily`, `weekly` or `monthly`', 'the feed language, available in [Trending page](https://github.com/trending/javascript?since=monthly) \'s URL, don\'t filter option is `any`', 'natural language, available in [Trending page](https://github.com/trending/javascript?since=monthly) \'s URL']" radar="1" rssbud="1" selfhost="1"/>
### Topics ### Topics

View File

@@ -237,7 +237,7 @@ GitHub 官方也提供了一些 RSS:
### Trending ### Trending
<Route author="DIYgod" example="/github/trending/daily/javascript/zh" path="/github/trending/:since/:language/:spoken_language?" :paramsDesc="['时间跨度,可在 [Trending 页](https://github.com/trending/javascript?since=monthly&spoken_language_code=zh) URL 中找到,可选 `daily` `weekly` `monthly`', '语言,可在 [Trending 页](https://github.com/trending/javascript?since=monthly&spoken_language_code=zh) URL 中找到,`any`表示不设语言限制', '自然语言,可在 [Trending 页](https://github.com/trending/javascript?since=monthly&spoken_language_code=zh) URL 中找到']" radar="1" rssbud="1"/> <Route author="DIYgod" example="/github/trending/daily/javascript/zh" path="/github/trending/:since/:language/:spoken_language?" :paramsDesc="['时间跨度,可在 [Trending 页](https://github.com/trending/javascript?since=monthly&spoken_language_code=zh) URL 中找到,可选 `daily` `weekly` `monthly`', '语言,可在 [Trending 页](https://github.com/trending/javascript?since=monthly&spoken_language_code=zh) URL 中找到,`any`表示不设语言限制', '自然语言,可在 [Trending 页](https://github.com/trending/javascript?since=monthly&spoken_language_code=zh) URL 中找到']" radar="1" rssbud="1" selfhost="1"/>
### Topics ### Topics

View File

@@ -1,59 +1,91 @@
const config = require('@/config').value;
const got = require('@/utils/got'); const got = require('@/utils/got');
const { art } = require('@/utils/render'); const { art } = require('@/utils/render');
const cheerio = require('cheerio'); const cheerio = require('cheerio');
const path = require('path'); const path = require('path');
module.exports = async (ctx) => { module.exports = async (ctx) => {
if (!config.github || !config.github.access_token) {
throw 'GitHub trending RSS is disabled due to the lack of <a href="https://docs.rsshub.app/install/#pei-zhi-bu-fen-rss-mo-kuai-pei-zhi">relevant config</a>';
}
const since = ctx.params.since; const since = ctx.params.since;
const language = ctx.params.language === 'any' ? '' : ctx.params.language; const language = ctx.params.language === 'any' ? '' : ctx.params.language;
const spoken_language = ctx.params.spoken_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', method: 'get',
url, url: trendingUrl,
headers: { 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 repos = Object.values(repoData.data).map((repo) => {
const found = trendingRepos.find((r) => `${r.owner}/${r.name}` === repo.nameWithOwner);
const $ = cheerio.load(data); return { ...found, ...repo };
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;
});
})
);
ctx.state.data = { ctx.state.data = {
title: $('title').text(), title: $('title').text(),
link: url, link: trendingUrl,
item: items, 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}`,
})),
}; };
}; };