fix(route/sjtu): gs (#12758)

* fix(route/sjtu): gs

* Correct "Incomplete URL substring sanitization"

* Use built-in etchArticle for Wechat article.

* Update lib/v2/sjtu/radar.js

* Fix missing semicolon.

* Update university.md

* refactor: migrate to v2

rename maintainer GitHub ID from `richardchien` to `stdrc`
https://github.com/DIYgod/RSSHub/pull/4064

* fix(radar): add default case

---------
This commit is contained in:
Andvari 2023-07-09 22:59:39 +08:00 committed by GitHub
parent 4e41f3e67d
commit b378116a71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 322 additions and 200 deletions

View File

@ -2576,11 +2576,11 @@ jsjxy.hbut.edu.cn 证书链不全,自建 RSSHub 可设置环境变量 NODE_TLS
### 电子信息与电气工程学院学术动态
<Route author="HenryQW" example="/sjtu/seiee/academic" path="/sjtu/seiee/academic"/>
<Route author="HenryQW" example="/sjtu/seiee/academic" path="/sjtu/seiee/academic" radar="1"/>
### 电子信息与电气工程学院本科教务办
<Route author="Polynomia" example="/sjtu/seiee/bjwb/major_select" path="/sjtu/seiee/bjwb/:type" :paramsDesc="['无默认选项']">
<Route author="Polynomia" example="/sjtu/seiee/bjwb/major_select" path="/sjtu/seiee/bjwb/:type" :paramsDesc="['无默认选项']" radar="1">
| 分专业 | 转专业 | 直升研究生 | 交换交流 | 国际办学 |
| ------------ | -------------- | ------------ | -------- | ------------- |
@ -2588,19 +2588,9 @@ jsjxy.hbut.edu.cn 证书链不全,自建 RSSHub 可设置环境变量 NODE_TLS
</Route>
### 研究生通知公告
<Route author="mzr1996" example="/sjtu/gs/tzgg/pyxx" path="/sjtu/gs/tzgg/:type?" :paramsDesc="['默认列举所有通知公告']">
| 通知公告 | 工作信息 | 招生信息 | 培养信息 | 学位学科 | 国际交流 | 创新工程 |
| -------- | -------- | -------- | -------- | -------- | -------- | -------- |
| 空 | gzxx | xwxx1 | pyxx | xwxx | gjjl | cxgc |
</Route>
### 电子信息与电气工程学院学生工作办公室
<Route author="Polynomia xxchan" example="/sjtu/seiee/xsb/news" path="/sjtu/seiee/xsb/:type?" :paramsDesc="['默认列举所有通知公告']">
<Route author="Polynomia xxchan" example="/sjtu/seiee/xsb/news" path="/sjtu/seiee/xsb/:type?" :paramsDesc="['默认列举所有通知公告']" radar="1">
| 信息通告 | 奖学金 | 助学金 | 讲座活动 | 党团活动 | 新闻发布 | 本科生综合测评 |
| -------- | ----------- | ------------ | -------- | -------- | -------- | -------------- |
@ -2608,9 +2598,31 @@ jsjxy.hbut.edu.cn 证书链不全,自建 RSSHub 可设置环境变量 NODE_TLS
</Route>
### 研究生通知公告
<Route author="mzr1996 dzx-dzx" example="/sjtu/gs/enroll/59" path="/sjtu/gs/:type/:num?" :paramsDesc="['类别', '细分类别, 仅对`type`为`enroll`或`exchange`有效']" radar="1">
| 工作信息 | 招生信息 | 培养信息 | 学位学科 | 国际交流 | 创新工程 |
| -------- | -------- | -------- | -------- | -------- | -------- |
| work | enroll | train | degree | exchange | xsjy |
当`type`为`enroll`, `num`可选字段:
| 58 | 59 | 60 | 61 | 62 |
| -------- | -------- | ---------- | -------- | -------- |
| 博士招生 | 硕士招生 | 港澳台招生 | 考点信息 | 院系动态 |
当`type`为`exchange`, `num`可选字段:
| 67 | 68 | 69 | 70 | 71 |
| -------------- | -------------- | -------------- | -------------- | -------------- |
| 国家公派研究生 | 国际化培养资助 | 校际交换与联培 | 交流与合作项目 | 项目招募与宣讲 |
</Route>
### 教务处通知公告
<Route author="SeanChao" example="/sjtu/jwc/" path="/sjtu/jwc/:type?" :paramsDesc="['默认为 all ']">
<Route author="SeanChao" example="/sjtu/jwc" path="/sjtu/jwc/:type?" :paramsDesc="['默认为 notice']" radar="1">
| 新闻中心 | 通知通告 | 教学运行 | 注册学务 | 研究办 | 教改办 | 综合办 | 语言文字 | 工会与支部 | 通识教育 |
| -------- | -------- | --------- | -------- | ------ | ------ | ------ | -------- | ---------- | -------- |
@ -2630,7 +2642,7 @@ jsjxy.hbut.edu.cn 证书链不全,自建 RSSHub 可设置环境变量 NODE_TLS
### 研究生招生网招考信息
<Route author="richardchien" example="/sjtu/yzb/zkxx/sszs" path="/sjtu/yzb/zkxx/:type" :paramsDesc="['无默认选项']">
<Route author="stdrc" example="/sjtu/yzb/zkxx/sszs" path="/sjtu/yzb/zkxx/:type" :paramsDesc="['无默认选项']" radar="1">
| 博士招生 | 硕士招生 | 港澳台招生 | 考点信息 | 院系动态 |
| -------- | -------- | ---------- | -------- | -------- |

View File

@ -541,14 +541,14 @@ router.get('/shanghaitech/activity', lazyloadRouteHandler('./routes/universities
router.get('/shanghaitech/sist/activity', lazyloadRouteHandler('./routes/universities/shanghaitech/sist/activity'));
// 上海交通大学
router.get('/sjtu/seiee/academic', lazyloadRouteHandler('./routes/universities/sjtu/seiee/academic'));
router.get('/sjtu/seiee/bjwb/:type', lazyloadRouteHandler('./routes/universities/sjtu/seiee/bjwb'));
router.get('/sjtu/seiee/xsb/:type?', lazyloadRouteHandler('./routes/universities/sjtu/seiee/xsb'));
// router.get('/sjtu/seiee/academic', lazyloadRouteHandler('./routes/universities/sjtu/seiee/academic'));
// router.get('/sjtu/seiee/bjwb/:type', lazyloadRouteHandler('./routes/universities/sjtu/seiee/bjwb'));
// router.get('/sjtu/seiee/xsb/:type?', lazyloadRouteHandler('./routes/universities/sjtu/seiee/xsb'));
router.get('/sjtu/gs/tzgg/:type?', lazyloadRouteHandler('./routes/universities/sjtu/gs/tzgg'));
router.get('/sjtu/jwc/:type?', lazyloadRouteHandler('./routes/universities/sjtu/jwc'));
router.get('/sjtu/tongqu/:type?', lazyloadRouteHandler('./routes/universities/sjtu/tongqu/activity'));
router.get('/sjtu/yzb/zkxx/:type', lazyloadRouteHandler('./routes/universities/sjtu/yzb/zkxx'));
// router.get('/sjtu/gs/tzgg/:type?', lazyloadRouteHandler('./routes/universities/sjtu/gs/tzgg'));
// router.get('/sjtu/jwc/:type?', lazyloadRouteHandler('./routes/universities/sjtu/jwc'));
// router.get('/sjtu/tongqu/:type?', lazyloadRouteHandler('./routes/universities/sjtu/tongqu/activity'));
// router.get('/sjtu/yzb/zkxx/:type', lazyloadRouteHandler('./routes/universities/sjtu/yzb/zkxx'));
// 江南大学
router.get('/ju/jwc/:type?', lazyloadRouteHandler('./routes/universities/ju/jwc'));

View File

@ -1,31 +0,0 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const util = require('./utils');
module.exports = async (ctx) => {
const type = ctx.params.type;
let url = '';
if (type) {
url = `http://www.gs.sjtu.edu.cn/index/tzgg/${type}.htm`;
} else {
url = 'http://www.gs.sjtu.edu.cn/index/tzgg.htm';
}
const response = await got({
method: 'get',
url,
});
const data = response.data;
const $ = cheerio.load(data);
const list = $('.comm li').slice(0, 10).get();
const result = await util.ProcessFeed(list, ctx.cache, url);
ctx.state.data = {
title: $('title').text(),
link: url,
item: result,
};
};

View File

@ -1,64 +0,0 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const url = require('url');
const ProcessFeed = async (list, caches, host) => {
const requestList = [];
const result = await Promise.all(
list.map(async (item) => {
const $ = cheerio.load(item);
const itemUrl = url.resolve(host, $('a').attr('href'));
const cache = await caches.get(itemUrl);
if (cache) {
return Promise.resolve(JSON.parse(cache));
}
const single = {
title: $('a span').text(),
link: itemUrl,
guid: itemUrl,
};
const es = got.get(itemUrl);
requestList.push(es);
return Promise.resolve(single);
})
);
const responses = await got.all(requestList);
for (let i = 0; i < responses.length; i++) {
const $ = cheerio.load(responses[i].data);
$('.ssdd a').each(function () {
if ($(this).attr('href')) {
$(this).attr('href', url.resolve(host, $(this).attr('href')));
}
});
$('.ssdd img').each(function () {
if ($(this).attr('src')) {
$(this).attr('src', url.resolve(host, $(this).attr('src')));
}
});
result[i].description = $('.ssdd').html();
const date = new Date(
$('.zz span:nth-child(2)')
.text()
.match(/\d{4}-\d{2}-\d{2}/)
);
const timeZone = 8;
const serverOffset = date.getTimezoneOffset() / 60;
result[i].pubDate = new Date(date.getTime() - 60 * 60 * 1000 * (timeZone + serverOffset)).toUTCString();
caches.set(result[i].link, JSON.stringify(result[i]));
}
return result;
};
module.exports = {
ProcessFeed,
};

View File

@ -1,45 +0,0 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const url = require('url');
const host = 'http://bjwb.seiee.sjtu.edu.cn';
module.exports = function (meta, extract) {
return async (ctx) => {
const { title, local, author } = meta(ctx);
const link = url.resolve(host, local);
const response = await got.get(link);
const list = extract(cheerio.load(response.data));
const out = await Promise.all(
list.map(async (item) => {
const itemUrl = url.resolve(host, item.link);
const cache = await ctx.cache.get(itemUrl);
if (cache) {
return Promise.resolve(JSON.parse(cache));
}
const response = await got.get(itemUrl);
const $ = cheerio.load(response.data);
const single = {
title: item.title,
link: itemUrl,
author,
description: $('.article_content').text(),
pubDate: new Date(item.date).toUTCString(),
};
ctx.cache.set(itemUrl, JSON.stringify(single));
return Promise.resolve(single);
})
);
ctx.state.data = {
title,
link,
item: out,
};
};
};

62
lib/v2/sjtu/gs.js Normal file
View File

@ -0,0 +1,62 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const timezone = require('@/utils/timezone');
const { fetchArticle } = require('@/utils/wechat-mp');
module.exports = async (ctx) => {
const type = ctx.params.type;
const num = ctx.params.num ?? '';
const rootUrl = 'https://www.gs.sjtu.edu.cn';
const currentUrl = `${rootUrl}/announcement/${type}/${num}`;
const response = await got({
method: 'get',
url: currentUrl,
});
const $ = cheerio.load(response.data);
const list = $('a.announcement-item')
.map((_, item) => {
item = $(item);
const day = item.find('.day').text().trim().replace('.', '-');
const year = item.find('.month').text().trim();
return {
title: item.find('.title').text().trim(),
link: `${item.attr('href').startsWith('http') ? '' : rootUrl}${item.attr('href')}`,
pubDate: timezone(parseDate(`${year}-${day}`, 'YYYY-MM-DD'), +8),
};
})
.get();
const items = await Promise.all(
list.map((item) =>
ctx.cache.tryGet(item.link, async () => {
if (new URL(item.link).hostname === 'mp.weixin.qq.com') {
item.description = (await fetchArticle(ctx, item.link)).description;
}
else {
const detailResponse = await got({
method: 'get',
url: item.link,
});
const content = cheerio.load(detailResponse.data);
item.description = content('.page-content').html();
}
return item;
})
)
);
ctx.state.data = {
title: `${num === '' ? '' : `${$('.category-nav-block .active').text().trim()} - `}${$('div.inner-banner-text .title').text().trim()} - ${$('title').text()}`,
link: currentUrl,
item: items,
};
};

View File

@ -22,11 +22,11 @@ async function getFullArticle(link) {
const absLink = new URL(relativeLink, urlRoot).href;
$(e).attr('href', absLink);
});
return content.html();
return content.html() + ($('.Newslist2').length ? $('.Newslist2').html() : '');
}
module.exports = async (ctx) => {
const type = ctx.params.type ?? 'all';
const type = ctx.params.type ?? 'notice';
const config = {
all: {
section: '通知通告',
@ -86,7 +86,8 @@ module.exports = async (ctx) => {
const out = await Promise.all(
$('body > div.list-box > div.container > div > div.ny_right > div > div.ny_right_con > div > ul')
.find('li')
.map(async (i, e) => {
.toArray()
.map((e) => {
const info = $(e).find('.wz');
const relativeLink = info.find('a').attr('href');
const link = new URL(relativeLink, sectionLink).href;
@ -95,16 +96,16 @@ module.exports = async (ctx) => {
const day = timeElement.find('h2').text();
const yearAndMonth = timeElement.find('p').text();
const pubDate = parseDate(`${yearAndMonth}.${day}`, 'YYYY.MM.DD');
const description = await ctx.cache.tryGet(link, () => getFullArticle(link));
const single = {
title,
link,
pubDate,
description,
};
return Promise.resolve(single);
return ctx.cache.tryGet(link, async () => {
const description = await getFullArticle(link);
return {
title,
link,
pubDate,
description,
};
});
})
.get()
);
ctx.state.data = {

View File

@ -0,0 +1,9 @@
module.exports = {
'/gs/:type/:num?': ['dzx-dzx'],
'/jwc/:type?': ['SeanChao'],
'/seiee/academic': ['HenryQW'],
'/seiee/bjwb/:type': ['Polynomia'],
'/seiee/xsb/:type?': ['Polynomia', 'xxchan'],
'/tongqu/:type?': ['SeanChao'],
'/yzb/zkxx/:type': ['stdrc'],
};

126
lib/v2/sjtu/radar.js Normal file
View File

@ -0,0 +1,126 @@
module.exports = {
'sjtu.edu.cn': {
_name: '上海交通大学',
'bjwb.seiee': [
{
title: '电子信息与电气工程学院',
docs: 'https://docs.rsshub.app/university.html#shang-hai-jiao-tong-da-xue',
source: ['/seiee/list/:type', '/bkjwb/list/:type', '/xsb/list/:type'],
target: (params) => {
let type = '';
switch (params.type) {
// /sjtu/seiee/academic
case '683-1-20.htm':
type = 'academic';
break;
// /sjtu/seiee/bjwb/:type
case '1503-1-20.htm':
type = 'bjwb/academic';
break;
case '1505-1-20.htm':
type = 'bjwb/academic';
break;
case '1506-1-20.htm':
type = 'bjwb/postgraduate';
break;
case '1507-1-20.htm':
type = 'bjwb/abroad';
break;
case '2281-1-20.htm':
type = 'bjwb/international';
break;
// /sjtu/seiee/xsb/:type?
case '2938-1-20.htm':
type = 'xsb/news';
break;
case '611-1-20.htm':
type = 'xsb/scholarship';
break;
case '2676-1-20.htm':
type = 'xsb/activity';
break;
case '1981-1-20.htm':
type = 'xsb/lecture';
break;
case '705-1-20.htm':
type = 'xsb/all';
break;
case '1001-1-20.htm':
type = 'xsb/financialAid';
break;
case '3016-1-20.htm':
type = 'xsb/zhcp';
break;
default:
return null;
}
return `/sjtu/seiee/${type}`;
},
},
],
gs: [
{
title: '研究生通知公告',
docs: 'https://docs.rsshub.app/university.html#shang-hai-jiao-tong-da-xue-yan-jiu-sheng-tong-zhi-gong-gao',
source: ['/announcement/:type'],
target: '/sjtu/gs/:type',
},
],
jwc: [
{
title: '教务处通知公告',
docs: 'https://docs.rsshub.app/university.html#shang-hai-jiao-tong-da-xue',
source: ['/xwtg/:type'],
target: (params) => {
let type = '';
switch (params.type) {
case 'xwzx.htm':
type = 'news';
break;
case 'tztg.htm':
case '':
type = 'notice';
break;
case 'jxyx.htm':
type = 'operation';
break;
case 'zcxw.htm':
type = 'affairs';
break;
case 'yjb.htm':
type = 'yjb';
break;
case 'jgb.htm':
type = 'jgb';
break;
case 'zhb.htm':
type = 'zhb';
break;
case 'yywz.htm':
type = 'language';
break;
case 'ghyzb.htm':
type = 'party';
break;
case 'tsjy.htm':
type = 'ge';
break;
default:
type = 'notice';
break;
}
return `/sjtu/jwc/${type}`;
},
},
],
tongqu: [{ title: '同去网最新活动', docs: 'https://docs.rsshub.app/university.html#shang-hai-jiao-tong-da-xue' }],
yzb: [
{
title: '研究生招生网招考信息',
docs: 'https://docs.rsshub.app/university.html#shang-hai-jiao-tong-da-xue',
source: ['/index/zkxx/:type'],
target: (params) => `/sjtu/yzb/zkxx/${params.type.replace('.htm', '')}`,
},
],
},
};

9
lib/v2/sjtu/router.js Normal file
View File

@ -0,0 +1,9 @@
module.exports = function (router) {
router.get('/gs/:type/:num?', require('./gs'));
router.get('/jwc/:type?', require('./jwc'));
router.get('/seiee/academic', require('./seiee/academic'));
router.get('/seiee/bjwb/:type', require('./seiee/bjwb'));
router.get('/seiee/xsb/:type?', require('./seiee/xsb'));
router.get('/tongqu/:type?', require('./tongqu/activity'));
router.get('/yzb/zkxx/:type', require('./yzb/zkxx'));
};

View File

@ -3,16 +3,15 @@ const workerFactory = require('./utils');
module.exports = workerFactory(
() => ({
title: '上海交通大学电子信息与电气工程学院本科教务办 -- 学术动态',
local: `seiee/list/683-1-20.htm`,
local: 'seiee/list/683-1-20.htm',
author: '上海交通大学电子信息与电气工程学院本科教务办',
}),
($) =>
$('.list_style_1 li')
.slice(0, 10)
.map((i, e) => ({
.toArray()
.map((e) => ({
date: $(e).children('span').text(),
title: $(e).children('a').text(),
title: $(e).children('a').attr('title'),
link: $(e).children('a').attr('href'),
}))
.get()
);

View File

@ -4,23 +4,23 @@ module.exports = workerFactory(
(ctx) => {
const config = {
abroad: {
link: `bkjwb/list/1507-1-20.htm`,
link: 'bkjwb/list/1507-1-20.htm',
title: '交换交流',
},
international: {
link: `bkjwb/list/2281-1-20.htm`,
link: 'bkjwb/list/2281-1-20.htm',
title: '国际办学通知',
},
major_select: {
link: `bkjwb/list/1503-1-20.htm`,
link: 'bkjwb/list/1503-1-20.htm',
title: '分专业',
},
major_transfer: {
link: `bkjwb/list/1505-1-20.htm`,
link: 'bkjwb/list/1505-1-20.htm',
title: '转专业',
},
postgraduate: {
link: `bkjwb/list/1506-1-20.htm`,
link: 'bkjwb/list/1506-1-20.htm',
title: '直升研究生',
},
};
@ -35,11 +35,10 @@ module.exports = workerFactory(
},
($) =>
$('.list_box_5 li')
.slice(0, 10)
.map((i, e) => ({
.toArray()
.map((e) => ({
date: $(e).children('span').text().slice(1, -1),
title: $(e).children('a').text().slice(2),
link: $(e).children('a').attr('href'),
}))
.get()
);

View File

@ -0,0 +1,40 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const host = 'https://bjwb.seiee.sjtu.edu.cn';
module.exports = function (meta, extract) {
return async (ctx) => {
const { title, local, author } = meta(ctx);
const link = new URL(local, host).href;
const response = await got(link);
const list = extract(cheerio.load(response.data));
const out = await Promise.all(
list.map((item) => {
const itemUrl = new URL(item.link, host).href;
return ctx.cache.tryGet(itemUrl, async () => {
const response = await got(itemUrl);
const $ = cheerio.load(response.data);
return {
title: item.title,
link: itemUrl,
author,
description: $('.article_content').html(),
pubDate: parseDate(item.date, 'YYYY-MM-DD'),
};
});
})
);
ctx.state.data = {
title,
link,
item: out,
};
};
};

View File

@ -43,11 +43,10 @@ module.exports = workerFactory(
},
($) =>
$('.list_box_5_2 li')
.slice(0, 10)
.map((i, e) => ({
.toArray()
.map((e) => ({
date: $(e).children('span').text().slice(1, -1),
title: $(e).children('a').text().slice(1),
link: $(e).children('a').attr('href'),
}))
.get()
);

View File

@ -0,0 +1,6 @@
{{ e.name }}<br>
开始时间: {{ e.sign_start_time }}<br>
结束时间: {{ e.sign_end_time }}<br>
地点: {{ e.location }}<br>
报名人数: {{ e.member_count }}/{{ e.max_member }}<br>
来自{{ e.source }}

View File

@ -1,5 +1,6 @@
const got = require('@/utils/got');
const url = require('url');
const { art } = require('@/utils/render');
const { join } = require('path');
const urlRoot = 'https://tongqu.sjtu.edu.cn';
@ -30,9 +31,9 @@ module.exports = async (ctx) => {
const activities = data.result.acts;
const feeds = activities.map((e) => ({
title: e.name,
link: url.resolve(urlRoot, '/act/' + e.actid),
link: new URL(`/act/${e.actid}`, urlRoot).href,
category: e.typename,
description: `${e.name}\r\n` + `开始时间: ${e.sign_start_time}\r\n结束时间: ${e.sign_end_time}\r\n` + `地点: ${e.location}\r\n` + `报名人数: ${e.member_count}/${e.max_member}\r\n` + `来自${e.source}\r\n`,
description: art(join(__dirname, '../templates/activity.art'), { e }),
}));
ctx.state.data = {

View File

@ -1,6 +1,6 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const url = require('url');
const { parseDate } = require('@/utils/parse-date');
const baseTitle = '上海交通大学研究生招生网招考信息';
const baseUrl = 'https://yzb.sjtu.edu.cn/index/zkxx/';
@ -22,12 +22,11 @@ module.exports = async (ctx) => {
link: pageUrl,
title: `${baseTitle} -- ${$('title').text().split('-')[0]}`,
item: $('li[id^="line"] a')
.slice(0, 10)
.map((_, elem) => ({
link: url.resolve(pageUrl, elem.attribs.href),
.toArray()
.map((elem) => ({
link: new URL(elem.attribs.href, pageUrl).href,
title: elem.attribs.title,
pubDate: new Date($(elem.next).text().trim()).toUTCString(),
}))
.get(),
pubDate: parseDate($(elem.next).text().trim()),
})),
};
};