diff --git a/assets/radar-rules.js b/assets/radar-rules.js
index a66587686a..ff82fb4846 100644
--- a/assets/radar-rules.js
+++ b/assets/radar-rules.js
@@ -227,6 +227,12 @@
source: '/:user',
target: '/github/starred_repos/:user',
},
+ {
+ title: '仓库 Contributors',
+ docs: 'https://docs.rsshub.app/programming.html#github',
+ source: ['/:user/:repo/graphs/contributors', '/:user/:repo'],
+ target: '/github/contributors/:user/:repo',
+ },
],
},
'zhihu.com': {
diff --git a/docs/en/programming.md b/docs/en/programming.md
index d8890c8904..005d0539dc 100644
--- a/docs/en/programming.md
+++ b/docs/en/programming.md
@@ -77,6 +77,10 @@ GitHub provides some official RSS feeds:
+### Repo Contributors
+
+
+
## GitLab
### Explore
diff --git a/docs/programming.md b/docs/programming.md
index 4b8a08c58b..3a9f60c993 100644
--- a/docs/programming.md
+++ b/docs/programming.md
@@ -127,6 +127,10 @@ GitHub 官方也提供了一些 RSS:
+### 仓库 Contirbutors
+
+
+
## GitLab
### Explore
diff --git a/lib/router.js b/lib/router.js
index 1051724506..ffdd62e26e 100644
--- a/lib/router.js
+++ b/lib/router.js
@@ -275,6 +275,8 @@ router.get('/github/search/:query/:sort?/:order?', require('./routes/github/sear
router.get('/github/branches/:user/:repo', require('./routes/github/branches'));
router.get('/github/file/:user/:repo/:branch/:filepath+', require('./routes/github/file'));
router.get('/github/starred_repos/:user', require('./routes/github/starred_repos'));
+router.get('/github/contributors/:user/:repo/:anon?', require('./routes/github/contributors'));
+
// f-droid
router.get('/fdroid/apprelease/:app', require('./routes/fdroid/apprelease'));
diff --git a/lib/routes/github/contributors.js b/lib/routes/github/contributors.js
new file mode 100644
index 0000000000..dfbff149e8
--- /dev/null
+++ b/lib/routes/github/contributors.js
@@ -0,0 +1,85 @@
+const got = require('@/utils/got');
+const config = require('@/config').value;
+
+module.exports = async (ctx) => {
+ const user = ctx.params.user;
+ const repo = ctx.params.repo;
+ const anon = ctx.params.anon;
+
+ const host = `https://github.com/${user}/${repo}`;
+ const url = `https://api.github.com/repos/${user}/${repo}/contributors?` + (anon ? 'anon=1' : '');
+
+ const headers = {};
+ if (config.github && config.github.access_token) {
+ headers.Authorization = `token ${config.github.access_token}`;
+ }
+
+ const response = await got({
+ method: 'get',
+ url,
+ headers,
+ });
+ let data = response.data;
+
+ try {
+ const last_page_link = response.headers.link.split(',').find(function(elem) {
+ return elem.includes('"last"');
+ });
+ const url_base = last_page_link.match(/<(.*)page=\d*/)[1];
+ const page_count = Number(last_page_link.match(/page=(\d*)/)[1]);
+
+ for (let page = 2; page <= page_count; page++) {
+ // eslint-disable-next-line no-await-in-loop
+ const response = await got({
+ method: 'get',
+ url: `${url_base}page=${page}`,
+ headers,
+ });
+ data = data.concat(response.data);
+ }
+ } catch (err) {
+ if (!(err instanceof TypeError)) {
+ throw err;
+ }
+ }
+
+ let title;
+ let description;
+ let link;
+ let guid;
+
+ const items = [];
+ let index = 0;
+
+ data.forEach(function(item) {
+ const time = new Date();
+ time.setMinutes(time.getMinutes() - data.length + index);
+ index++;
+
+ if (item.type === 'Anonymous') {
+ title = `Contributor: ${item.name}`;
+ description = `
Anonymous contributor
Name: ${item.name}
E-mail: ${item.email}
Contributions: ${item.contributions}
`;
+ link = '';
+ guid = `anon-${item.name}`;
+ } else {
+ title = `Contributor: ${item.login}`;
+ description = `
${item.login}
Contributions: ${item.contributions}
`;
+ link = item.html_url;
+ guid = item.id;
+ }
+
+ items.push({
+ title: title,
+ description: description,
+ guid: guid,
+ link: link,
+ pubDate: time,
+ });
+ }),
+ (ctx.state.data = {
+ title: `${user}/${repo} Contributors`,
+ link: `${host}/graphs/contributors`,
+ description: `New contributors for ${user}/${repo}`,
+ item: items,
+ });
+};