mirror of
https://github.com/DIYgod/RSSHub.git
synced 2025-12-05 04:11:26 +08:00
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:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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}`,
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user