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',
},