diff --git a/docs/en/programming.md b/docs/en/programming.md index 82348427dc1e6c..1d7c0e3a49a01c 100644 --- a/docs/en/programming.md +++ b/docs/en/programming.md @@ -213,7 +213,7 @@ For instance, the `/github/topics/framework/l=php&o=desc&s=stars` route will gen ### Issue / Pull Request comments - + ### Wiki History diff --git a/docs/programming.md b/docs/programming.md index 614eafbc0e77ca..647e3bae03fa12 100644 --- a/docs/programming.md +++ b/docs/programming.md @@ -308,7 +308,7 @@ GitHub 官方也提供了一些 RSS: ### Issue / Pull Request 评论 - + ### Wiki 历史 diff --git a/docs/university.md b/docs/university.md index c676883c082ba5..90f61d3c12d635 100644 --- a/docs/university.md +++ b/docs/university.md @@ -3669,6 +3669,13 @@ jsjxy.hbut.edu.cn 证书链不全,自建 RSSHub 可设置环境变量 NODE_TLS +### 选课信息教务通知 + + +::: warning 注意 +由于选课通知仅允许校园网访问,需自行部署。 +::: + ## 中国科学技术大学 ### 官网通知公告 diff --git a/lib/v2/bloomberg/react-renderer.js b/lib/v2/bloomberg/react-renderer.js index 00fc468c666d77..2298f1bc65a20a 100644 --- a/lib/v2/bloomberg/react-renderer.js +++ b/lib/v2/bloomberg/react-renderer.js @@ -1,6 +1,39 @@ const art = require('art-template'); const path = require('path'); -const { processVideo } = require('./utils'); +const got = require('@/utils/got'); + +const headers = { + accept: 'application/json', + 'cache-control': 'no-cache', + referer: 'https://www.bloomberg.com', +}; + +const processVideo = async (bmmrId, summary) => { + const api = `https://www.bloomberg.com/multimedia/api/embed?id=${bmmrId}`; + const res = await got(api, { headers }); + + // Blocked by PX3, return the default + const redirectUrls = res.redirectUrls.map(String); + if (redirectUrls.some((r) => new URL(r).pathname === '/tosv2.html')) { + return { + stream: '', + mp4: '', + coverUrl: '', + caption: summary, + }; + } + + if (res.data) { + const video_json = res.data; + return { + stream: video_json.streams ? video_json.streams[0]?.url : '', + mp4: video_json.downloadURLs ? video_json.downloadURLs['600'] : '', + coverUrl: video_json.thumbnail?.baseUrl ?? '', + caption: video_json.description || video_json.title || summary, + }; + } + return {}; +}; const nodeRenderers = { paragraph: async (node, nextNode) => `

${await nextNode(node.content)}

`, @@ -128,9 +161,9 @@ const nodeRenderers = { } if (t === 'video') { const h = node.data; - const id = h.attachment && h.attachment.id; - const v = await processVideo(id); - return v; + const id = h.attachment?.id; + const desc = await processVideo(id, h.attachment?.title); + return art(path.join(__dirname, 'templates/video_media.art'), desc); } if (t === 'audio' && node.data.attachment) { const B = node.data.attachment; @@ -187,4 +220,6 @@ const documentToHtmlString = async (document) => { module.exports = { documentToHtmlString, + processVideo, + headers, }; diff --git a/lib/v2/bloomberg/utils.js b/lib/v2/bloomberg/utils.js index 2f42ac910f1726..419194459893ed 100644 --- a/lib/v2/bloomberg/utils.js +++ b/lib/v2/bloomberg/utils.js @@ -6,7 +6,7 @@ const { parseDate } = require('@/utils/parse-date'); const got = require('@/utils/got'); const { art } = require('@/utils/render'); -const { documentToHtmlString } = require('./react-renderer'); +const { documentToHtmlString, processVideo, headers } = require('./react-renderer'); const rootUrl = 'https://www.bloomberg.com/feeds'; const sel = 'script[data-component-props="ArticleBody"], script[data-component-props="FeatureBody"]'; @@ -40,11 +40,6 @@ const apiEndpoints = { sel: 'script#__SSR_DATA__', }, }; -const headers = { - accept: 'application/json', - 'cache-control': 'no-cache', - referer: 'https://www.bloomberg.com', -}; const pageTypeRegex1 = /\/(?[\w-]*?)\/(?\d{4}-\d{2}-\d{2}\/.*)/; const pageTypeRegex2 = /(?features\/|graphics\/)(?.*)/; @@ -84,7 +79,7 @@ const parseArticle = (item, ctx) => if (group) { const { page, link } = group; if (apiEndpoints[page]) { - const api = apiEndpoints[page]; + const api = { ...apiEndpoints[page] }; let res; try { @@ -95,6 +90,7 @@ const parseArticle = (item, ctx) => if (err.name && (err.name === 'HTTPError' || err.name === 'RequestError')) { try { res = await got(item.link, { headers }); + api.useOrigLink = true; } catch (err) { // return the default one return { @@ -106,7 +102,7 @@ const parseArticle = (item, ctx) => } } - // Blocked by PX3, or 404 by api, return the default + // Blocked by PX3, or 404 by both api and direct link, return the default const redirectUrls = res.redirectUrls.map(String); if (redirectUrls.some((r) => new URL(r).pathname === '/tosv2.html') || res.statusCode === 404) { return { @@ -257,11 +253,15 @@ const parseFeaturePage = async (res, api, item) => { }; const parseOtherPage = async function (res, api, item) { + if (api.useOrigLink) { + return parseNewsletterPage(res, apiEndpoints.newsletters, item); + } const article_json = JSON.parse( cheerio .load(res.data.html ?? res.data)(api.sel) .html() ); + const story_json = article_json.story; const body_html = story_json.body; const media_img = story_json.ledeImageUrl || Object.values(story_json.imageAttachments ?? {})[0]?.baseUrl; @@ -332,33 +332,6 @@ const processLedeMedia = async (story_json) => { } }; -const processVideo = async (bmmrId, summary) => { - const api = `https://www.bloomberg.com/multimedia/api/embed?id=${bmmrId}`; - const res = await got(api, { headers }); - - // Blocked by PX3, return the default - const redirectUrls = res.redirectUrls.map(String); - if (redirectUrls.some((r) => new URL(r).pathname === '/tosv2.html')) { - return { - stream: '', - mp4: '', - coverUrl: '', - caption: summary, - }; - } - - if (res.data) { - const video_json = res.data; - return { - stream: video_json.streams ? video_json.streams[0]?.url : '', - mp4: video_json.downloadURLs ? video_json.downloadURLs['600'] : '', - coverUrl: video_json.thumbnail?.baseUrl ?? '', - caption: video_json.description || video_json.title || summary, - }; - } - return {}; -}; - const processBody = async (body_html, story_json) => { const removeSel = ['meta', 'script', '*[class$="-footnotes"]', '*[class$="for-you"]', '*[class$="-newsletter"]', '*[class$="page-ad"]', '*[class$="-recirc"]', '*[data-ad-placeholder="Advertisement"]']; @@ -436,5 +409,4 @@ module.exports = { asyncPoolAll, parseNewsList, parseArticle, - processVideo, }; diff --git a/lib/v2/github/comments.js b/lib/v2/github/comments.js index 41e9a24859aef8..bc228f1cfa6e44 100644 --- a/lib/v2/github/comments.js +++ b/lib/v2/github/comments.js @@ -3,12 +3,16 @@ const { parseDate } = require('@/utils/parse-date'); const md = require('markdown-it')({ html: true, }); +const rootUrl = 'https://github.com'; const apiUrl = 'https://api.github.com'; const config = require('@/config').value; const typeDict = { issue: { title: 'Issue', }, + issues: { + title: 'Issue', + }, pull: { title: 'Pull request', }, @@ -17,8 +21,8 @@ const typeDict = { module.exports = async (ctx) => { const user = ctx.params.user; const repo = ctx.params.repo; - const number = isNaN(parseInt(ctx.params.number)) ? 1 : parseInt(ctx.params.number); - const limit = ctx.query.limit ? parseInt(ctx.params.limit) : 100; + const number = ctx.params.number && isNaN(parseInt(ctx.params.number)) ? 1 : parseInt(ctx.params.number); + const limit = ctx.query.limit ? parseInt(ctx.query.limit) : 100; const headers = config.github && config.github.access_token ? { @@ -29,6 +33,64 @@ module.exports = async (ctx) => { Accept: 'application/vnd.github.v3+json', }; + if (isNaN(number)) { + await allIssues(ctx, user, repo, limit, headers); + } else { + await singleIssue(ctx, user, repo, number, limit, headers); + } +}; + +async function allIssues(ctx, user, repo, limit, headers) { + const response = await got(`${apiUrl}/repos/${user}/${repo}/issues/comments`, { + headers, + searchParams: { + sort: 'updated', + direction: 'desc', + per_page: limit, + }, + }); + + const timeline = response.data; + + const items = timeline.map((item) => { + const actor = item.actor?.login ?? item.user?.login ?? 'ghost'; + const issueUrlParts = item.issue_url.split('/'); + const issue = issueUrlParts[issueUrlParts.length - 1]; + const urlParts = item.html_url.split('/'); + const issueType = typeDict[urlParts[urlParts.length - 2]].title; + + return { + title: `${actor} commented on ${user}/${repo}: ${issueType} #${issue}`, + author: actor, + pubDate: parseDate(item.created_at), + link: item.html_url, + description: item.body ? md.render(item.body) : null, + }; + }); + + const rateLimit = { + limit: parseInt(response.headers['x-ratelimit-limit']), + remaining: parseInt(response.headers['x-ratelimit-remaining']), + reset: parseDate(parseInt(response.headers['x-ratelimit-reset']) * 1000), + resoure: response.headers['x-ratelimit-resource'], + used: parseInt(response.headers['x-ratelimit-used']), + }; + + ctx.state.data = { + title: `${user}/${repo}: Issue & Pull request comments`, + link: `${rootUrl}/${user}/${repo}`, + item: items, + }; + + ctx.state.json = { + title: `${user}/${repo}: Issue & Pull request comments`, + link: `${rootUrl}/${user}/${repo}`, + item: items, + rateLimit, + }; +} + +async function singleIssue(ctx, user, repo, number, limit, headers) { const response = await got(`${apiUrl}/repos/${user}/${repo}/issues/${number}`, { headers, }); @@ -124,4 +186,4 @@ module.exports = async (ctx) => { used: parseInt(response.headers['x-ratelimit-used']), }, }; -}; +} diff --git a/lib/v2/github/maintainer.js b/lib/v2/github/maintainer.js index 9874c658139b6e..0cdc6b716f5fe4 100644 --- a/lib/v2/github/maintainer.js +++ b/lib/v2/github/maintainer.js @@ -1,6 +1,6 @@ module.exports = { '/branches/:user/:repo': ['max-arnold'], - '/comments/:user/:repo/:number': ['TonyRL'], + '/comments/:user/:repo/:number?': ['TonyRL', 'FliegendeWurst'], '/contributors/:user/:repo/:order?/:anon?': ['zoenglinghou'], '/file/:user/:repo/:branch/:filepath+': ['zengxs'], '/gist/:gistId': ['TonyRL'], diff --git a/lib/v2/github/radar.js b/lib/v2/github/radar.js index 0f9848f4816b2f..46f9488e9d8836 100644 --- a/lib/v2/github/radar.js +++ b/lib/v2/github/radar.js @@ -14,6 +14,12 @@ module.exports = { source: ['/:user/:repo/:type/:number'], target: '/github/comments/:user/:repo/:number', }, + { + title: 'Issue & Pull Request comments', + docs: 'https://docs.rsshub.app/programming.html#github', + source: ['/:user/:repo/:type'], + target: '/github/comments/:user/:repo', + }, { title: '仓库 Contributors', docs: 'https://docs.rsshub.app/programming.html#github', diff --git a/lib/v2/github/router.js b/lib/v2/github/router.js index 3c16200c3c3abf..7b8f52b4a2de80 100644 --- a/lib/v2/github/router.js +++ b/lib/v2/github/router.js @@ -1,7 +1,7 @@ module.exports = function (router) { router.get('/branches/:user/:repo', require('./branches')); router.get('/comments/:user/:repo/:type/:number', require('./comments')); // deprecated - router.get('/comments/:user/:repo/:number', require('./comments')); + 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('/gist/:gistId', require('./gist')); diff --git a/lib/v2/ouc/jwgl.js b/lib/v2/ouc/jwgl.js new file mode 100644 index 00000000000000..6f2a8826242936 --- /dev/null +++ b/lib/v2/ouc/jwgl.js @@ -0,0 +1,42 @@ +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const { parseDate } = require('@/utils/parse-date'); + +module.exports = async (ctx) => { + const link = 'http://jwgl.ouc.edu.cn/public/listSchoolNotices.action?currentPage=1&recordsPerPage=15&qtitle='; + const response = await got(link); + const $ = cheerio.load(response.data); + const list = $('div.datalist table tbody tr') + .toArray() + .map((e) => { + e = $(e); + const noticeId = e + .find('a') + .attr('onclick') + .match(/viewNotice\('(.+?)'\)/)[1]; + const tds = e.find('td'); + return { + title: tds.eq(2).text(), + link: 'http://jwgl.ouc.edu.cn/public/viewSchoolNoticeDetail.action?schoolNoticeId=' + noticeId, + pubDate: parseDate(tds.eq(3).text(), 'YYYY-MM-DD HH:mm'), + }; + }); + + const out = await Promise.all( + list.map((item) => + ctx.cache.tryGet(item.link, async () => { + const response = await got(item.link); + const $ = cheerio.load(response.data); + item.description = $('div.notice').html(); + return item; + }) + ) + ); + + ctx.state.data = { + title: '中国海洋大学选课信息教务通知', + link, + description: '中国海洋大学选课信息教务通知', + item: out, + }; +}; diff --git a/lib/v2/ouc/maintainer.js b/lib/v2/ouc/maintainer.js index dc547d7720641d..7cb365c2b22930 100644 --- a/lib/v2/ouc/maintainer.js +++ b/lib/v2/ouc/maintainer.js @@ -2,5 +2,6 @@ module.exports = { '/it/postgraduate': ['shengmaosu'], '/it/:type?': ['GeoffreyChen777'], '/jwc': ['3401797899'], + '/jwgl': ['3401797899'], '/yjs': ['shengmaosu'], }; diff --git a/lib/v2/ouc/radar.js b/lib/v2/ouc/radar.js index 6399cbc262a517..5b5a6e6cb8fbb3 100644 --- a/lib/v2/ouc/radar.js +++ b/lib/v2/ouc/radar.js @@ -1,14 +1,6 @@ module.exports = { 'ouc.edu.cn': { _name: '中国海洋大学', - jwc: [ - { - title: '教务处', - docs: 'https://docs.rsshub.app/university.html#zhong-guo-hai-yang-da-xue', - source: ['/', '/6517/list.htm'], - target: '/ouc/jwc', - }, - ], it: [ { title: '信息科学与工程学院', @@ -23,6 +15,22 @@ module.exports = { target: '/ouc/it/postgraduate', }, ], + jwc: [ + { + title: '教务处', + docs: 'https://docs.rsshub.app/university.html#zhong-guo-hai-yang-da-xue', + source: ['/', '/6517/list.htm'], + target: '/ouc/jwc', + }, + ], + jwgl: [ + { + title: '选课信息教务通知', + docs: 'https://docs.rsshub.app/university.html#zhong-guo-hai-yang-da-xue', + source: ['/cas/login.action', '/public/SchoolNotice.jsp'], + target: '/ouc/jwgl', + }, + ], yz: [ { title: '研究生院', diff --git a/lib/v2/ouc/router.js b/lib/v2/ouc/router.js index 96f4ae329736f0..5ca3225918d4a6 100644 --- a/lib/v2/ouc/router.js +++ b/lib/v2/ouc/router.js @@ -2,5 +2,6 @@ module.exports = (router) => { router.get('/it/postgraduate', require('./it-postgraduate')); router.get('/it/:type?', require('./it')); router.get('/jwc', require('./jwc')); + router.get('/jwgl', require('./jwgl')); router.get('/yjs', require('./yjs')); }; diff --git a/lib/v2/segmentfault/user.js b/lib/v2/segmentfault/user.js index 98fa8b9f0b1e8b..2ea5510c0d6efe 100644 --- a/lib/v2/segmentfault/user.js +++ b/lib/v2/segmentfault/user.js @@ -1,7 +1,7 @@ const got = require('@/utils/got'); const cheerio = require('cheerio'); const { parseDate } = require('@/utils/parse-date'); - +const { acw_sc__v2 } = require('./utils'); const host = 'https://segmentfault.com'; module.exports = async (ctx) => { @@ -19,10 +19,16 @@ module.exports = async (ctx) => { author, })); + const acwScV2Cookie = await acw_sc__v2(list[0].link, ctx.cache.tryGet); + const items = await Promise.all( list.map((item) => ctx.cache.tryGet(item.link, async () => { - const response = await got(item.link); + const response = await got(item.link, { + headers: { + cookie: `acw_sc__v2=${acwScV2Cookie};`, + }, + }); const content = cheerio.load(response.data); item.description = content('article') diff --git a/lib/v2/segmentfault/utils.js b/lib/v2/segmentfault/utils.js new file mode 100644 index 00000000000000..463b2a914fc7cc --- /dev/null +++ b/lib/v2/segmentfault/utils.js @@ -0,0 +1,34 @@ +const zlib = require('zlib'); +const got = require('@/utils/got'); +const config = require('@/config').value; +const { getAcwScV2ByArg1 } = require('@/v2/5eplay/utils'); + +const acw_sc__v2 = (link, tryGet) => + tryGet( + 'segmentfault:acw_sc__v2', + async () => { + const response = await got(link, { + decompress: false, + }); + + const unzipData = zlib.createUnzip(); + unzipData.write(response.body); + + let acw_sc__v2 = ''; + for await (const data of unzipData) { + const strData = data.toString(); + const matches = strData.match(/var arg1='(.*?)';/); + if (matches) { + acw_sc__v2 = getAcwScV2ByArg1(matches[1]); + break; + } + } + return acw_sc__v2; + }, + config.cache.routeExpire, + false + ); + +module.exports = { + acw_sc__v2, +}; diff --git a/lib/v2/utgd/timeline.js b/lib/v2/utgd/timeline.js index 343a0e266796d0..9646e88c294ba1 100644 --- a/lib/v2/utgd/timeline.js +++ b/lib/v2/utgd/timeline.js @@ -23,7 +23,7 @@ module.exports = async (ctx) => { ctx.cache.tryGet(`untag-${item.id}`, async () => { const detailResponse = await got({ method: 'get', - url: `${rootUrl}/api/v2/pages/${item.id}/`, + url: `${rootUrl}/api/v2/article/${item.id}/`, searchParams: { fields: 'article_description,article_category(category_name),article_tag(tag_name)', }, diff --git a/lib/v2/utgd/topic.js b/lib/v2/utgd/topic.js index 62b57bead044b1..8716cc57f55869 100644 --- a/lib/v2/utgd/topic.js +++ b/lib/v2/utgd/topic.js @@ -40,7 +40,7 @@ module.exports = async (ctx) => { ctx.cache.tryGet(`untag-${item.id}`, async () => { const detailResponse = await got({ method: 'get', - url: `${rootUrl}/api/v2/pages/${item.id}/`, + url: `${rootUrl}/api/v2/article/${item.id}/`, searchParams: { fields: 'article_description', },