diff --git a/docs/en/install/README.md b/docs/en/install/README.md index 58b117500b..77ffd4f315 100644 --- a/docs/en/install/README.md +++ b/docs/en/install/README.md @@ -534,7 +534,7 @@ See docs of the specified route and `lib/config.js` for detailed information. - `EH_SK`: The value of `sk` in the cookie header after logging in E-Hentai - `EH_IGNEOUS`: The value of `igneous` in the cookie header after logging in ExHentai. If this value is set, RSS will be generated from ExHentai, `EH_SK` will be ignored. -- github: [Access Token application](https://github.com/settings/tokens) +- GitHub: [Access Token application](https://github.com/settings/tokens) - `GITHUB_ACCESS_TOKEN`: GitHub Access Token diff --git a/docs/en/programming.md b/docs/en/programming.md index e703c21f51..d705226d04 100644 --- a/docs/en/programming.md +++ b/docs/en/programming.md @@ -191,9 +191,9 @@ For instance, the `/github/topics/framework/l=php&o=desc&s=stars` route will gen -### Issues / Pull Requests comments +### Issue / Pull Request comments - + ## GitLab diff --git a/docs/install/README.md b/docs/install/README.md index 0ab9499515..b29ba9cd3c 100644 --- a/docs/install/README.md +++ b/docs/install/README.md @@ -565,7 +565,7 @@ RSSHub 支持使用访问密钥 / 码,白名单和黑名单三种方式进行 - `EH_SK`: E-Hentai 账户登录后 cookie 中的`sk`值 - `EH_IGNEOUS`: ExHentai 账户登录后 cookie 中的`igneous`值。若设置此值,RSS 数据将全部从里站获取,`EH_SK`将被忽略 -- github 全部路由:[申请地址](https://github.com/settings/tokens) +- GitHub 全部路由:[申请地址](https://github.com/settings/tokens) - `GITHUB_ACCESS_TOKEN`: GitHub Access Token diff --git a/docs/programming.md b/docs/programming.md index 8f919efc1d..c4671fea64 100644 --- a/docs/programming.md +++ b/docs/programming.md @@ -269,9 +269,9 @@ GitHub 官方也提供了一些 RSS: -### Issues / Pull Requests 评论 +### Issue / Pull Request 评论 - + ## GitLab diff --git a/lib/v2/github/comments.js b/lib/v2/github/comments.js index 2313481cac..d0c1487799 100644 --- a/lib/v2/github/comments.js +++ b/lib/v2/github/comments.js @@ -1,64 +1,122 @@ const got = require('@/utils/got'); -const cheerio = require('cheerio'); const { parseDate } = require('@/utils/parse-date'); -const { art } = require('@/utils/render'); -const path = require('path'); - +const md = require('markdown-it')({ + html: true, +}); +const apiUrl = 'https://api.github.com'; +const config = require('@/config').value; const typeDict = { - issues: { - typeRootUrl: '/issues', + issue: { + title: 'Issue', }, pull: { - typeRootUrl: '/pulls', + title: 'Pull request', }, }; module.exports = async (ctx) => { const user = ctx.params.user; const repo = ctx.params.repo; - const type = ctx.params.type ?? 'issues'; - const number = ctx.params.number ?? '1'; - - const rootUrl = 'https://github.com'; + const number = isNaN(parseInt(ctx.params.number)) ? 1 : parseInt(ctx.params.number); + const limit = ctx.query.limit ? parseInt(ctx.params.limit) : 100; + const headers = + config.github && config.github.access_token + ? { + Accept: 'application/vnd.github.v3+json', + Authorization: `token ${config.github.access_token}`, + } + : { + Accept: 'application/vnd.github.v3+json', + }; const response = await got({ - method: 'get', - url: `${rootUrl}/${user}/${repo}/${type}/${number}`, - header: { - Referer: `${rootUrl}/${user}/${repo}${typeDict[type].typeRootUrl}`, + url: `${apiUrl}/repos/${user}/${repo}/issues/${number}`, + headers, + }); + const issue = response.data; + const type = issue.pull_request ? 'pull' : 'issue'; + + const timelineResponse = await got({ + url: issue.timeline_url, + headers, + searchParams: { + per_page: limit, }, }); + const timeline = timelineResponse.data; - const $ = cheerio.load(response.data); + let items = [ + { + title: `${issue.user.login} created ${user}/${repo}: ${typeDict[type].title} #${issue.number}`, + description: md.render(issue.body), + author: issue.user.login, + pubDate: parseDate(issue.created_at), + link: `${issue.html_url}#issue-${issue.id}`, + }, + ]; - const list = $('div.timeline-comment-group.js-minimizable-comment-group.js-targetable-element.TimelineItem-body.my-0') - .map((_, item) => { - item = $(item); - return { - title: `${item.find('strong.css-truncate').text().trim()} commented`, - description: art(path.join(__dirname, 'templates/comments-description.art'), { - desc: item.find('div.edit-comment-hide').html(), - }), - author: item.find('strong.css-truncate').text().trim(), - pubDate: parseDate(item.find('relative-time.no-wrap').attr('datetime')), - link: `${rootUrl}/${user}/${repo}/${type}/${number}` + item.find('a.Link--secondary.js-timestamp').attr('href'), - }; - }) - .get(); + timeline.forEach((item) => { + switch (item.event) { + case 'closed': + items.push({ + title: `${item.actor.login} ${item.event} ${user}/${repo}: ${typeDict[type].title} #${issue.number}`, + author: item.actor.login, + pubDate: parseDate(item.created_at), + link: item.url, + }); + break; + case 'commented': + items.push({ + title: `${item.actor.login} ${item.event} on ${user}/${repo}: ${typeDict[type].title} #${issue.number}`, + description: md.render(item.body), + author: item.actor.login, + pubDate: parseDate(item.created_at), + link: item.html_url, + }); + break; + case 'cross-referenced': + items.push({ + title: `${item.actor.login} ${item.event} on ${user}/${repo}: ${typeDict[type].title} #${issue.number}`, + description: `${item.actor.login} mentioned this issue in ${item.source.issue.title} #${item.source.issue.number}`, + author: item.actor.login, + pubDate: parseDate(item.created_at), + guid: `${item.actor.login} ${item.event} on ${user}/${repo}: ${typeDict[type].title} #${issue.number} on ${item.created_at}`, + }); + break; + case 'renamed': + items.push({ + title: `${item.actor.login} ${item.event} on ${user}/${repo}: ${typeDict[type].title} #${issue.number}`, + description: `${item.actor.login} changed the title ${item.rename.from} ${item.rename.to}`, + author: item.actor.login, + pubDate: parseDate(item.created_at), + link: item.url, + }); + break; + case 'reviewed': + items.push({ + title: `${item.user.login} ${item.event} on ${user}/${repo}: ${typeDict[type].title} #${issue.number}`, + description: item.body ? md.render(item.body) : item.state.replace('_', ' '), + author: item.user.login, + pubDate: parseDate(item.submitted_at), + link: item.html_url, + }); + break; + default: + break; + } + }); - const items = await Promise.all(list && list.map((item) => ctx.cache.tryGet(item.link, () => item))); + items = await Promise.all(items.map((item) => ctx.cache.tryGet(item.link, () => item))); ctx.state.data = { - title: $('span.author.flex-self-stretch').text().trim() + '/' + $('strong.mr-2.flex-self-stretch').text().trim() + `: ${type} #${number}`, - description: $('span.author.flex-self-stretch').text().trim() + '/' + $('strong.mr-2.flex-self-stretch').text().trim() + ': ' + $('h1.gh-header-title.mb-2.lh-condensed.f1.mr-0.flex-auto.break-word').text().trim(), - link: `${rootUrl}/${user}/${repo}/${type}/${number}`, + title: `${user}/${repo}: ${typeDict[type].title} #${number} - ${issue.title}`, + link: issue.html_url, item: items, }; ctx.state.json = { - title: $('span.author.flex-self-stretch').text().trim() + '/' + $('strong.mr-2.flex-self-stretch').text().trim() + `: ${type} #${number}`, - description: $('span.author.flex-self-stretch').text().trim() + '/' + $('strong.mr-2.flex-self-stretch').text().trim() + ': ' + $('h1.gh-header-title.mb-2.lh-condensed.f1.mr-0.flex-auto.break-word').text().trim(), - link: `${rootUrl}/${user}/${repo}/${type}/${number}`, + title: `${user}/${repo}: ${typeDict[type].title} #${number} - ${issue.title}`, + link: issue.html_url, item: items, }; }; diff --git a/lib/v2/github/follower.js b/lib/v2/github/follower.js index 2121d3d915..3f8b7b1170 100644 --- a/lib/v2/github/follower.js +++ b/lib/v2/github/follower.js @@ -3,7 +3,7 @@ const config = require('@/config').value; module.exports = async (ctx) => { if (!config.github || !config.github.access_token) { - throw 'GitHub follower RSS is disabled due to the lack of relevant config'; + throw 'GitHub follower RSS is disabled due to the lack of relevant config'; } const user = ctx.params.user; diff --git a/lib/v2/github/radar.js b/lib/v2/github/radar.js index 2adcf8bb79..21f8d6a2c3 100644 --- a/lib/v2/github/radar.js +++ b/lib/v2/github/radar.js @@ -6,7 +6,7 @@ module.exports = { title: 'Issues / Pull Requests 评论', docs: 'https://docs.rsshub.app/programming.html#github', source: ['/:user/:repo/:type/:number'], - target: '/github/comments/:user/:repo/:type/:number', + target: '/github/comments/:user/:repo/:number', }, { title: '用户仓库', diff --git a/lib/v2/github/router.js b/lib/v2/github/router.js index 54b517de88..2e78ef95b7 100644 --- a/lib/v2/github/router.js +++ b/lib/v2/github/router.js @@ -1,6 +1,7 @@ module.exports = function (router) { router.get('/branches/:user/:repo', require('./branches')); - router.get('/comments/:user/:repo/:type/:number', require('./comments')); + router.get('/comments/:user/:repo/:type/:number', require('./comments')); // deprecated + router.get('/comments/:user/:repo/:number', require('./comments')); router.get('/contributors/:user/:repo/:order?/:anon?', require('./contributors')); router.get('/file/:user/:repo/:branch/:filepath+', require('./file')); router.get('/issue/:user/:repo/:state?/:labels?', require('./issue')); diff --git a/lib/v2/github/star.js b/lib/v2/github/star.js index d44ea03bd3..d7c3dee5b6 100644 --- a/lib/v2/github/star.js +++ b/lib/v2/github/star.js @@ -3,7 +3,7 @@ const config = require('@/config').value; module.exports = async (ctx) => { if (!config.github || !config.github.access_token) { - throw 'GitHub star RSS is disabled due to the lack of relevant config'; + throw 'GitHub star RSS is disabled due to the lack of relevant config'; } const user = ctx.params.user; const repo = ctx.params.repo; diff --git a/lib/v2/github/starred_repos.js b/lib/v2/github/starred_repos.js index 25b218a8f4..2de70ad4a6 100644 --- a/lib/v2/github/starred_repos.js +++ b/lib/v2/github/starred_repos.js @@ -3,7 +3,7 @@ const config = require('@/config').value; module.exports = async (ctx) => { if (!config.github || !config.github.access_token) { - throw 'GitHub star RSS is disabled due to the lack of relevant config'; + throw 'GitHub star RSS is disabled due to the lack of relevant config'; } const user = ctx.params.user;