feat(route): 山东大学(威海)新闻网 (#9537)

* feat(sduwh): add extractors.

* feat(route): add route for 山东大学(威海)新闻网

* docs: for route sduwh/news

* docs: for route sduwh/news

(cherry picked from commit 831830167a)

* feat(radar): for route 山东大学(威海)新闻网

* refactor: change `got.get` to `got`.

* refactor: prefer `parseDate()` to `new Date()`

Co-authored-by: Tony <TonyRL@users.noreply.github.com>

* fix: incomplete URL substring sanitization.

Make CodeQL happy.

* fix(radar): fix target field.

* fix: change route /sduwh to /sdu/wh

* fix: remove superfluous slash character in url.

* feat: look for exact date first.

* feat: extract exact date from news extractor.

* feat: extract exact date from view extractor.

* feat: extractor for www.sdrj.sdu.edu.cn

* refactor: semantic separation of sduwh with sdu

* feat(radar): more accurate name

* docs: update documentation

* refactor: migrate to v2

* refactor: fix deprecated url.resolve

* fix: update docs url

Co-authored-by: Tony <TonyRL@users.noreply.github.com>

* fix: sdu not working routes

* fix: accurate `ctx.state.data.url`

Co-authored-by: Tony <TonyRL@users.noreply.github.com>

* fix: better error handling for extractors.

* fix: timezone

Co-authored-by: Tony <TonyRL@users.noreply.github.com>

* fix: better error handling.

Co-authored-by: Tony <TonyRL@users.noreply.github.com>
This commit is contained in:
Levi Zim
2022-04-17 00:01:39 +08:00
committed by GitHub
parent 5622641efa
commit 958be6266e
22 changed files with 660 additions and 290 deletions

View File

@@ -1961,7 +1961,7 @@ type 列表:
### 软件学院通知
<Route author="Ji4n1ng" example="/sdu/sc/0" path="/sdu/sc/:type?" :paramsDesc="['默认为 `0`']">
<Route author="Ji4n1ng" example="/sdu/sc/0" path="/sdu/sc/:type?" :paramsDesc="['默认为 `0`']" radar="1" rssbud="1">
| 通知公告 | 学术动态 | 本科教育 | 研究生教育 |
| ---- | ---- | ---- | ----- |
@@ -1971,7 +1971,7 @@ type 列表:
### 材料科学与工程学院通知
<Route author="Ji4n1ng" example="/sdu/cmse/0" path="/sdu/cmse/:type?" :paramsDesc="['默认为 `0`']">
<Route author="Ji4n1ng" example="/sdu/cmse/0" path="/sdu/cmse/:type?" :paramsDesc="['默认为 `0`']" radar="1" rssbud="1">
| 通知公告 | 学院新闻 | 本科生教育 | 研究生教育 | 学术动态 |
| ---- | ---- | ----- | ----- | ---- |
@@ -1981,7 +1981,7 @@ type 列表:
### 机械工程学院通知
<Route author="Ji4n1ng" example="/sdu/mech/0" path="/sdu/mech/:type?" :paramsDesc="['默认为 `0`']">
<Route author="Ji4n1ng" example="/sdu/mech/0" path="/sdu/mech/:type?" :paramsDesc="['默认为 `0`']" radar="1" rssbud="1">
| 通知公告 | 院所新闻 | 教学信息 | 学术动态 | 学院简报 |
| ---- | ---- | ---- | ---- | ---- |
@@ -1991,7 +1991,7 @@ type 列表:
### 能源与动力工程学院通知
<Route author="Ji4n1ng" example="/sdu/epe/0" path="/sdu/epe/:type?" :paramsDesc="['默认为 `0`']">
<Route author="Ji4n1ng" example="/sdu/epe/0" path="/sdu/epe/:type?" :paramsDesc="['默认为 `0`']" radar="1" rssbud="1">
| 学院动态 | 通知公告 | 学术论坛 |
| ---- | ---- | ---- |
@@ -2001,14 +2001,26 @@ type 列表:
### 计算机科学与技术学院通知
<Route author="suxb201" example="/sdu/cs/0" path="/sdu/cs/:type?" :paramsDesc="['默认为 `0`']">
<Route author="suxb201" example="/sdu/cs/0" path="/sdu/cs/:type?" :paramsDesc="['默认为 `0`']" radar="1" rssbud="1">
| 学院公告 | 学术报告 | 新闻动态 |
| 学院公告 | 学术报告 | 科技简讯 |
| ---- | ---- | ---- |
| 0 | 1 | 2 |
</Route>
## 山东大学(威海)
### 新闻网
<Route author="kxxt" example="/sdu/wh/news/xyyw" path="/sdu/wh/news/:column?" :paramsDesc="['专栏名称,默认为校园要闻(`xyyw`']" radar="1" rssbud="1">
| 校园要闻 | 学生动态 | 综合新闻 | 山大视点 | 菁菁校园 | 校园简讯 | 玛珈之窗 | 热点专题 | 媒体视角 | 高教视野 | 理论学习 |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| xyyw | xsdt | zhxw | sdsd | jjxy | xyjx | mjzc | rdzt | mtsj | gjsy | llxx |
</Route>
## 上海大学
### 上海大学官网信息

View File

@@ -716,12 +716,12 @@ router.get('/jgsu/jwc', lazyloadRouteHandler('./routes/universities/jgsu/jwc'));
// 中南大学
router.get('/csu/job/:type?', lazyloadRouteHandler('./routes/universities/csu/job'));
// 山东大学
router.get('/sdu/sc/:type?', lazyloadRouteHandler('./routes/universities/sdu/sc'));
router.get('/sdu/cs/:type?', lazyloadRouteHandler('./routes/universities/sdu/cs'));
router.get('/sdu/cmse/:type?', lazyloadRouteHandler('./routes/universities/sdu/cmse'));
router.get('/sdu/mech/:type?', lazyloadRouteHandler('./routes/universities/sdu/mech'));
router.get('/sdu/epe/:type?', lazyloadRouteHandler('./routes/universities/sdu/epe'));
// 山东大学 migrated to v2
// router.get('/sdu/sc/:type?', lazyloadRouteHandler('./routes/universities/sdu/sc'));
// router.get('/sdu/cs/:type?', lazyloadRouteHandler('./routes/universities/sdu/cs'));
// router.get('/sdu/cmse/:type?', lazyloadRouteHandler('./routes/universities/sdu/cmse'));
// router.get('/sdu/mech/:type?', lazyloadRouteHandler('./routes/universities/sdu/mech'));
// router.get('/sdu/epe/:type?', lazyloadRouteHandler('./routes/universities/sdu/epe'));
// 中国海洋大学
router.get('/ouc/it/:type?', lazyloadRouteHandler('./routes/universities/ouc/it'));

View File

@@ -1,55 +0,0 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const url = require('url');
const host = 'http://www.cmse.sdu.edu.cn/';
const typelist = ['通知公告', '学院新闻', '本科生教育', '研究生教育', '学术动态'];
const urlList = ['zxzx/tzgg.htm', 'zxzx/xyxw.htm', 'zxzx/bksjy.htm', 'zxzx/yjsjy.htm', 'zxzx/xsdt.htm'];
module.exports = async (ctx) => {
const type = parseInt(ctx.params.type) || 0;
const link = url.resolve(host, urlList[type]);
const response = await got.get(link);
const $ = cheerio.load(response.data);
const list = $('#list_right_list a')
.slice(0, 10)
.map((i, e) => $(e).attr('href'))
.get();
const out = await Promise.all(
list
.filter((e) => e.startsWith('../info'))
.map(async (itemUrl) => {
itemUrl = url.resolve(host, itemUrl.slice('3'));
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 rawDate = $('#show_info').text().split(/\s{4}/);
let date = rawDate[0].split('')[1];
date = date.slice(0, 4) + '-' + date.slice(5, 7) + '-' + date.slice(8, 10) + ' ' + date.slice(12);
const single = {
title: $('#show_title').text().trim(),
link: itemUrl,
author: '山东大学材料科学与工程学院',
description: $('#show_content').html(),
pubDate: new Date(date),
};
ctx.cache.set(itemUrl, JSON.stringify(single));
return Promise.resolve(single);
})
);
ctx.state.data = {
title: `山东大学材料科学与工程学院${typelist[type]}`,
link,
item: out,
};
};

View File

@@ -1,55 +0,0 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const url = require('url');
const host = 'http://www.cs.sdu.edu.cn/';
const typelist = ['学院公告', '学术报告', '新闻动态'];
const urlList = ['index/xygg.htm', 'xwgg/xsbg.htm', 'xwgg/xyxw.htm'];
module.exports = async (ctx) => {
const type = parseInt(ctx.params.type) || 0;
const link = url.resolve(host, urlList[type]);
const response = await got.get(link);
const $ = cheerio.load(response.data);
const dateDict = {};
const list = $('.sub_text .news-list')
.slice(0, 10)
.map((i, e) => {
const divs = $(e).children();
const tlink = 'http://www.cs.sdu.edu.cn/' + $('a', divs[1]).attr('href').substring(3);
dateDict[tlink] = new Date($(divs[2]).text()).toUTCString();
return tlink;
})
.get();
const out = await Promise.all(
list.map(async (itemUrl) => {
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: $('#newsTitle').text().trim(),
author: '山东大学计算机科学与技术学院',
description: $('.v_news_content').html(),
pubDate: dateDict[itemUrl],
link: itemUrl,
};
ctx.cache.set(itemUrl, JSON.stringify(single));
return Promise.resolve(single);
})
);
ctx.state.data = {
title: `山东大学计算机科学与技术学院${typelist[type]}通知`,
link,
item: out,
};
};

View File

@@ -1,54 +0,0 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const url = require('url');
const host = 'http://www.epe.sdu.edu.cn/';
const typelist = ['学院动态', '通知公告', '学术论坛'];
const urlList = ['zxzx/xydt.htm', 'zxzx/tzgg.htm', 'zxzx/xslt.htm'];
module.exports = async (ctx) => {
const type = parseInt(ctx.params.type) || 0;
const link = url.resolve(host, urlList[type]);
const response = await got.get(link);
const $ = cheerio.load(response.data);
const list = $('#page_right_main li a')
.slice(0, 10)
.map((i, e) => $(e).attr('href'))
.get();
const out = await Promise.all(
list
.filter((e) => e.startsWith('../info'))
.map(async (itemUrl) => {
itemUrl = url.resolve(host, itemUrl.slice('3'));
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 rawDate = $('#show_info').text().split(/\s{4}/);
const date = rawDate[0].split('')[1];
const single = {
title: $('#show_title').text().trim(),
link: itemUrl,
author: '山东大学能源与动力工程学院',
description: $('#show_content').html(),
pubDate: new Date(date),
};
ctx.cache.set(itemUrl, JSON.stringify(single));
return Promise.resolve(single);
})
);
ctx.state.data = {
title: `山东大学能源与动力工程学院${typelist[type]}`,
link,
item: out,
};
};

View File

@@ -1,58 +0,0 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const url = require('url');
const typelist = ['通知公告', '院所新闻', '教学信息', '学术动态', '学院简报'];
const urlList = ['xwdt/tzgg.htm', 'xwdt/ysxw.htm', 'xwdt/jxxx.htm', 'xwdt/xsdt.htm', 'xwdt/xyjb.htm'];
const host = 'http://www.mech.sdu.edu.cn/';
module.exports = async (ctx) => {
const type = parseInt(ctx.params.type) || 0;
const link = url.resolve(host, urlList[type]);
const response = await got.get(link);
const $ = cheerio.load(response.data);
const list = $('#page_list li a')
.slice(0, 10)
.map((i, e) => $(e).attr('href'))
.get();
const out = await Promise.all(
list
.filter((e) => e.startsWith('../info') || e.startsWith('http://www.rd.sdu.edu.cn/'))
.map(async (itemUrl) => {
const isFromMech = itemUrl.startsWith('../info');
if (isFromMech) {
itemUrl = url.resolve(host, itemUrl.slice('3'));
}
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 rawDate = $('#show_info').text().split(/\s{4}/);
let date = rawDate[0].split('')[1];
date = date.slice(0, 4) + '-' + date.slice(5, 7) + '-' + date.slice(8, 10) + ' ' + date.slice(11);
const single = {
title: $('#show_title').text().trim(),
link: itemUrl,
author: '山东大学机械工程学院',
description: $('#show_content').html(),
pubDate: new Date(date),
};
ctx.cache.set(itemUrl, JSON.stringify(single));
return Promise.resolve(single);
})
);
ctx.state.data = {
title: `山东大学机械工程学院${typelist[type]}`,
link,
item: out,
};
};

View File

@@ -1,56 +0,0 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const url = require('url');
const host = 'http://www.sc.sdu.edu.cn/';
const typelist = ['通知公告', '学术动态', '本科教育', '研究生教育'];
const urlList = ['tzgg.htm', 'kxyj/xsdt/2.htm', 'rcpy/bkjy.htm', 'rcpy/yjsjy.htm'];
module.exports = async (ctx) => {
const type = parseInt(ctx.params.type) || 0;
const link = url.resolve(host, urlList[type]);
const response = await got.get(link);
const $ = cheerio.load(response.data);
const dateDict = {};
const list = $('.lm_list li')
.slice(0, 10)
.map((i, e) => {
let aLink = $(e).children('a').attr('href');
// aLink = aLink.slice(3);
aLink = url.resolve(host, aLink);
const date = $(e).children('span').text().trim();
dateDict[aLink] = date;
return aLink;
})
.get();
const out = await Promise.all(
list.map(async (itemUrl) => {
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: $('h1.c-title').text().trim(),
link: itemUrl,
author: '山东大学软件学院',
description: $('.v_news_content').html(),
pubDate: new Date(dateDict[itemUrl]),
};
ctx.cache.set(itemUrl, JSON.stringify(single));
return Promise.resolve(single);
})
);
ctx.state.data = {
title: `山东大学软件学院${typelist[type]}`,
link,
item: out,
};
};

View File

@@ -5,6 +5,7 @@
* lib/route/tencent/wechat
* lib/v2/wechat
* lib/v2/gzh360
* lib/v2/sdu/cs
*
* If your new route is not in the above folders, please add it to the list.
*

59
lib/v2/sdu/cmse.js Normal file
View File

@@ -0,0 +1,59 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const host = 'http://www.cmse.sdu.edu.cn/';
const typelist = ['通知公告', '学院新闻', '本科生教育', '研究生教育', '学术动态'];
const urlList = ['zxzx/tzgg.htm', 'zxzx/xyxw.htm', 'zxzx/bksjy.htm', 'zxzx/yjsjy.htm', 'zxzx/xsdt.htm'];
module.exports = async (ctx) => {
const type = ctx.params.type ? parseInt(ctx.params.type) : 0;
const link = new URL(urlList[type], host).href;
const response = await got(link);
const $ = cheerio.load(response.data);
let item = $('.article_list li')
.map((_, e) => {
e = $(e);
const a = e.find('a');
return {
title: a.text().trim(),
link: a.attr('href'),
pubDate: parseDate(e.find('.date').text(), 'YYYY/MM/DD'),
};
})
.get();
item = await Promise.all(
item
.filter((e) => e.link.startsWith('../info'))
.map((item) => {
item.link = new URL(item.link.slice('3'), host).href;
return ctx.cache.tryGet(item.link, async () => {
const response = await got(item.link);
const $ = cheerio.load(response.data);
item.title = $('.contentTitle').text();
item.author =
$('.contentTitle2')
.find('span')
.eq(1)
.text()
.trim()
.match(/作者:(.*)/)[1] || '山东大学材料科学与工程学院';
$('.contentTitle, .contentTitle2').remove();
item.description = $('.content_detail').html();
return item;
});
})
);
ctx.state.data = {
title: `山东大学材料科学与工程学院${typelist[type]}`,
description: $('title').text(),
link,
item,
};
};

57
lib/v2/sdu/cs.js Normal file
View File

@@ -0,0 +1,57 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const { finishArticleItem } = require('@/utils/wechat-mp');
const host = 'https://www.cs.sdu.edu.cn/';
const typelist = ['学院公告', '学术报告', '科技简讯'];
const urlList = ['xygg.htm', 'xsbg.htm', 'kjjx.htm'];
module.exports = async (ctx) => {
const type = ctx.params.type ? parseInt(ctx.params.type) : 0;
const link = new URL(urlList[type], host).href;
const response = await got(link);
const $ = cheerio.load(response.data);
let item = $('.dqlb ul li')
.map((_, e) => {
e = $(e);
const a = e.find('a');
return {
title: a.text().trim(),
link: a.attr('href').startsWith('info/') ? host + a.attr('href') : a.attr('href'),
pubDate: parseDate(e.find('.fr').text().trim(), 'YYYY-MM-DD'),
};
})
.get();
item = await Promise.all(
item.map((item) =>
ctx.cache.tryGet(item.link, async () => {
if (new URL(item.link).hostname === 'mp.weixin.qq.com') {
return finishArticleItem(ctx, item);
} else if (new URL(item.link).hostname !== 'www.cs.sdu.edu.cn') {
return item;
}
const response = await got(item.link);
const $ = cheerio.load(response.data);
item.title = $('.xqnr_tit h2').text().trim();
item.author = $('.xqnr_tit span').eq(1).text().trim().replace('编辑:', '') || '山东大学计算机科学与技术学院';
$('.xqnr_tit').remove();
item.description = $('form[name=_newscontent_fromname]').html();
return item;
})
)
);
ctx.state.data = {
title: `山东大学计算机科学与技术学院${typelist[type]}通知`,
description: $('title').text(),
link,
item,
};
};

61
lib/v2/sdu/data.js Normal file
View File

@@ -0,0 +1,61 @@
module.exports = {
wh: {
news: {
name: '山东大学(威海)新闻网',
route: '/news',
source: ['/'],
titlePrefix: '(威海)新闻网|',
docs: 'https://docs.rsshub.app/university.html#shan-dong-da-xue-wei-hai',
getTarget(url) {
return this.route + '/' + url.replace(/\.htm$/, '');
},
url: 'https://xinwen.wh.sdu.edu.cn/',
columns: {
xyyw: {
name: '校园要闻',
url: 'xyyw.htm',
},
xsdt: {
name: '学生动态',
url: 'xsdt.htm',
},
zhxw: {
name: '综合新闻',
url: 'zhxw.htm',
},
sdsd: {
name: '山大视点',
url: 'sdsd.htm',
},
jjxy: {
name: '菁菁校园',
url: 'jjxy.htm',
},
xyjx: {
name: '校园简讯',
url: 'xyjx.htm',
},
mjzc: {
name: '玛珈之窗',
url: 'mjzc.htm',
},
rdzt: {
name: '热点专题',
url: 'rdzt.htm',
},
mtsj: {
name: '媒体视角',
url: 'mtsj.htm',
},
gjsy: {
name: '高教视野',
url: 'gjsy.htm',
},
llxx: {
name: '理论学习',
url: 'llxx.htm',
},
},
},
},
};

55
lib/v2/sdu/epe.js Normal file
View File

@@ -0,0 +1,55 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const timezone = require('@/utils/timezone');
const host = 'https://www.epe.sdu.edu.cn/';
const typelist = ['学院动态', '通知公告', '学术论坛'];
const urlList = ['zxzx/xydt.htm', 'zxzx/tzgg.htm', 'zxzx/xslt.htm'];
module.exports = async (ctx) => {
const type = ctx.params.type ? parseInt(ctx.params.type) : 0;
const link = new URL(urlList[type], host).href;
const response = await got(link);
const $ = cheerio.load(response.data);
let item = $('#page_right_main li a')
.map((_, e) => {
e = $(e);
return {
title: e.attr('title'),
link: e.attr('href'),
};
})
.get();
item = await Promise.all(
item
.filter((e) => e.link.startsWith('../info'))
.map((item) => {
item.link = new URL(item.link.slice('3'), host).href;
return ctx.cache.tryGet(item.link, async () => {
const response = await got(item.link);
const $ = cheerio.load(response.data);
const info = $('#show_info').text().split(/\s{4}/);
const date = info[0].split('')[1];
item.title = $('#show_title').text().trim();
item.author = info[1].replace('编辑:', '') || '山东大学能源与动力工程学院';
item.description = $('#show_content').html();
item.pubDate = timezone(parseDate(date), +8);
return item;
});
})
);
ctx.state.data = {
title: `山东大学能源与动力工程学院${typelist[type]}`,
description: $('title').text(),
link,
item,
};
};

View File

@@ -0,0 +1,12 @@
module.exports = async (link, ctx) => {
if (link.startsWith('https://xinwen.wh.sdu.edu.cn/')) {
return await require('./wh/news')(link, ctx);
}
if (link.startsWith('https://www.view.sdu.edu.cn/')) {
return await require('./view')(link, ctx);
}
if (link.startsWith('https://www.sdrj.sdu.edu.cn/')) {
return await require('./sdrj')(link, ctx);
}
return {};
};

View File

@@ -0,0 +1,21 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const timezone = require('@/utils/timezone');
module.exports = async (link, ctx) =>
await ctx.cache.tryGet(link, async () => {
let content, author, exactDate;
try {
const result = await got(link);
const $ = cheerio.load(result.data);
content = $('#vsb_content').html();
author = $("form[name='_newscontent_fromname'] > h1").text();
const exactDateLine = $("form[name='_newscontent_fromname'] > p.info").text().trim();
const exactDateText = exactDateLine.match(/^发布时间:(?<date>\d+\/\d+\/\d+\s\d{2}:\d{2}:\d{2})/).groups.date;
exactDate = timezone(parseDate(exactDateText, 'YYYY/MM/DD HH:mm:ss'), +8);
return { description: content, author, exactDate };
} catch (e) {
return { description: content, author, exactDate };
}
});

View File

@@ -0,0 +1,21 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const timezone = require('@/utils/timezone');
module.exports = async (link, ctx) =>
await ctx.cache.tryGet(link, async () => {
let content, author, exactDate;
try {
const result = await got(link);
const $ = cheerio.load(result.data);
content = $('#vsb_content').html();
author = $("form[name='_newscontent_fromname'] > div > p:last-of-type").text();
const exactDateLine = $('.news_tit > p:last-child').text();
const exactDateText = exactDateLine.match(/^发布日期:(?<date>\d+年\d+月\d+日\s\d{2}:\d{2})/).groups.date;
exactDate = timezone(parseDate(exactDateText, 'YYYY年MM月DD日 HH:mm'), +8);
return { description: content, author, exactDate };
} catch (e) {
return { description: content, author, exactDate };
}
});

View File

@@ -0,0 +1,21 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const timezone = require('@/utils/timezone');
module.exports = async (link, ctx) =>
await ctx.cache.tryGet(link, async () => {
let content, author, exactDate;
try {
const result = await got(link);
const $ = cheerio.load(result.data);
content = $('#main-content').html();
author = $('#source').text();
const exactDateLine = $('.news_tit > p:last-child').text();
const exactDateText = exactDateLine.match(/^发布日期:(?<date>\d+年\d+月\d+日\s\d{2}:\d{2})/).groups.date;
exactDate = timezone(parseDate(exactDateText, 'YYYY年MM月DD日 HH:mm'), +8);
return { description: content, author, exactDate };
} catch (e) {
return { description: content, author, exactDate };
}
});

8
lib/v2/sdu/maintainer.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
'/cmse/:type?': ['Ji4n1ng'],
'/cs/:type?': ['Ji4n1ng'],
'/epe/:type?': ['Ji4n1ng'],
'/mech/:type?': ['Ji4n1ng'],
'/sc/:type?': ['Ji4n1ng'],
'/wh/news/:column?': ['kxxt'],
};

60
lib/v2/sdu/mech.js Normal file
View File

@@ -0,0 +1,60 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const timezone = require('@/utils/timezone');
const typelist = ['通知公告', '院所新闻', '教学信息', '学术动态', '学院简报'];
const urlList = ['xwdt/tzgg.htm', 'xwdt/ysxw.htm', 'xwdt/jxxx.htm', 'xwdt/xsdt.htm', 'xwdt/xyjb.htm'];
const host = 'https://www.mech.sdu.edu.cn/';
module.exports = async (ctx) => {
const type = ctx.params.type ? parseInt(ctx.params.type) : 0;
const link = new URL(urlList[type], host).href;
const response = await got(link);
const $ = cheerio.load(response.data);
let item = $('#page_list li a')
.slice(0, 1)
.map((_, e) => {
e = $(e);
return {
title: e.attr('title'),
link: e.attr('href'),
};
})
.get();
item = await Promise.all(
item
.filter((e) => e.link.startsWith('../info') || e.link.startsWith('https://www.rd.sdu.edu.cn/'))
.map((item) => {
const isFromMech = item.link.startsWith('../info');
if (isFromMech) {
item.link = new URL(item.link.slice('3'), host).href;
}
return ctx.cache.tryGet(item.link, async () => {
const response = await got(item.link);
const $ = cheerio.load(response.data);
const info = $('#show_info').text().split(/\s{4}/);
const date = info[0].split('')[1];
item.title = $('#show_title').text().trim();
item.author = info[1].replace('作者:', '') || '山东大学机械工程学院';
$('#show_title, #show_info').remove();
item.description = $('form[name=_newscontent_fromname] div').html();
item.pubDate = timezone(parseDate(date), +8);
return item;
});
})
);
ctx.state.data = {
title: `山东大学机械工程学院${typelist[type]}`,
description: $('title').text(),
link,
item,
};
};

153
lib/v2/sdu/radar.js Normal file
View File

@@ -0,0 +1,153 @@
const { wh } = require('./data');
module.exports = {
'sdu.edu.cn': {
_name: '山东大学',
'xinwen.wh': Object.entries(wh.news.columns).map(([, value]) => ({
title: wh.news.titlePrefix + value.name,
docs: wh.news.docs,
source: wh.news.source,
target: '/sdu/wh' + wh.news.getTarget(value.url),
})),
'www.cmse': [
{
title: '材料科学与工程学院通知',
docs: 'https://docs.rsshub.app/university.html#shan-dong-da-xue',
source: ['/*path', '/'],
target: (params) => {
let type;
switch (params.path) {
case 'zxzx/tzgg.htm':
type = '0';
break;
case 'zxzx/xyxw.htm':
type = '1';
break;
case 'zxzx/bksjy.htm':
type = '2';
break;
case 'zxzx/yjsjy.htm':
type = '3';
break;
case 'zxzx/xsdt.htm':
type = '4';
break;
default:
type = '0';
break;
}
return `/sdu/cmse/${type}`;
},
},
],
'www.cs': [
{
title: '计算机科学与技术学院通知',
docs: 'https://docs.rsshub.app/university.html#shan-dong-da-xue',
source: ['/*path', '/'],
target: (params) => {
let type;
switch (params.path) {
case 'xygg.htm':
type = '0';
break;
case 'xsbg.htm':
type = '1';
break;
case 'kjjx.htm':
type = '2';
break;
default:
type = '0';
break;
}
return `/sdu/cs/${type}`;
},
},
],
'www.epe': [
{
title: '能源与动力工程学院通知',
docs: 'https://docs.rsshub.app/university.html#shan-dong-da-xue',
source: ['/*path', '/'],
target: (params) => {
let type;
switch (params.path) {
case 'zxzx/xydt.htm':
type = '0';
break;
case 'zxzx/tzgg.htm':
type = '1';
break;
case 'zxzx/xslt.htm':
type = '2';
break;
default:
type = '0';
break;
}
return `/sdu/epe/${type}`;
},
},
],
'www.mech': [
{
title: '机械工程学院通知',
docs: 'https://docs.rsshub.app/university.html#shan-dong-da-xue',
source: ['/*path', '/'],
target: (params) => {
let type;
switch (params.path) {
case 'xwdt/tzgg.htm':
type = '0';
break;
case 'xwdt/ysxw.htm':
type = '1';
break;
case 'xwdt/jxxx.htm':
type = '2';
break;
case 'xwdt/xsdt.htm':
type = '3';
break;
case 'xwdt/xyjb.htm':
type = '4';
break;
default:
type = '0';
break;
}
return `/sdu/mech/${type}`;
},
},
],
'www.sc': [
{
title: '软件学院通知',
docs: 'https://docs.rsshub.app/university.html#shan-dong-da-xue',
source: ['/*path', '/'],
target: (params) => {
let type;
switch (params.path) {
case 'tzgg.htm':
type = '0';
break;
case 'kxyj/xsyg.htm':
type = '1';
break;
case 'rcpy/bkjy.htm':
type = '2';
break;
case 'rcpy/yjsjy.htm':
type = '3';
break;
default:
type = '0';
break;
}
return `/sdu/sc/${type}`;
},
},
],
},
};

8
lib/v2/sdu/router.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = function (router) {
router.get('/cmse/:type?', require('./cmse'));
router.get('/cs/:type?', require('./cs'));
router.get('/epe/:type?', require('./epe'));
router.get('/mech/:type?', require('./mech'));
router.get('/sc/:type?', require('./sc'));
router.get('/wh/news/:column?', require('./wh/news'));
};

61
lib/v2/sdu/sc.js Normal file
View File

@@ -0,0 +1,61 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const { parseDate } = require('@/utils/parse-date');
const host = 'https://www.sc.sdu.edu.cn/';
const typelist = ['通知公告', '学术动态', '本科教育', '研究生教育'];
const urlList = ['tzgg.htm', 'kxyj/xsyg.htm', 'rcpy/bkjy.htm', 'rcpy/yjsjy.htm'];
module.exports = async (ctx) => {
const type = ctx.params.type ? parseInt(ctx.params.type) : 0;
const link = new URL(urlList[type], host).href;
const response = await got(link);
const $ = cheerio.load(response.data);
let item = $('.newlist01 li')
.map((_, e) => {
e = $(e);
const a = e.find('a');
let link = a.attr('href');
link = new URL(link, host).href;
return {
title: a.text().trim(),
link,
pubDate: parseDate(e.find('.date').text().trim()),
};
})
.get();
item = await Promise.all(
item.map((item) =>
ctx.cache.tryGet(item.link, async () => {
try {
const response = await got(item.link);
const $ = cheerio.load(response.data);
item.title = $('h3').text();
item.author =
$('.pr')
.text()
.trim()
.match(/作者:(.*)/)[1] || '山东大学软件学院';
$('h3, .pr').remove();
item.description = $('.content').html();
return item;
} catch (e) {
// intranet oa.sdu.edu.cn
return item;
}
})
)
);
ctx.state.data = {
title: `山东大学软件学院${typelist[type]}`,
description: $('title').text(),
link,
item,
};
};

38
lib/v2/sdu/wh/news.js Normal file
View File

@@ -0,0 +1,38 @@
const got = require('@/utils/got');
const cheerio = require('cheerio');
const data = require('../data').wh.news;
const extractor = require('../extractor');
const { parseDate } = require('@/utils/parse-date');
module.exports = async (ctx) => {
const column = ctx.params.column ?? 'xyyw';
const baseUrl = data.url;
const response = await got(baseUrl + data.columns[column].url);
const $ = cheerio.load(response.data);
const items = $('.n_newslist li');
const out = await Promise.all(
items.map(async (index, item) => {
item = $(item);
const anchor = item.find('a');
const title = anchor.attr('title');
const href = anchor.attr('href');
const link = href.startsWith('http') ? href : baseUrl + href;
const { description, author, exactDate } = await ctx.cache.tryGet(link, async () => await extractor(link, ctx));
const span = item.find('span');
const pubDate = exactDate ?? parseDate(span.text(), 'YYYY/MM/DD');
return {
title,
link,
description,
pubDate,
author,
};
})
);
ctx.state.data = {
title: `${data.name} ${data.columns[column].name}`,
link: baseUrl + data.columns[column].url,
item: out,
};
};