From db9bb6dc9156af6de2292df3ce83044d507ce6e6 Mon Sep 17 00:00:00 2001 From: RicardoMing Date: Mon, 21 Oct 2019 12:03:49 +0800 Subject: [PATCH] feat: add support for BUPT portal (#3290) --- docs/university.md | 10 ++ lib/config.js | 4 + lib/router.js | 1 + lib/routes/universities/bupt/portal.js | 172 +++++++++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 lib/routes/universities/bupt/portal.js diff --git a/docs/university.md b/docs/university.md index 59682036bf..3eca5f4024 100644 --- a/docs/university.md +++ b/docs/university.md @@ -104,6 +104,16 @@ pageClass: routes +### 信息门户 + + + +::: warning 注意 +信息门户的通知需要通过统一身份认证后才能获取,因此需要在校园网或校园 VPN 环境下自建。 + +设置环境变量: `BUPT_USERNAME` 用户名为学号, `BUPT_PASSWORD` 统一身份认证的密码。 +::: + ## 常州大学 ### 教务处 diff --git a/lib/config.js b/lib/config.js index f3e5b4cdd9..744017db76 100644 --- a/lib/config.js +++ b/lib/config.js @@ -98,6 +98,10 @@ const calculateValue = () => { chuiniu: { member: envs.CHUINIU_MEMBER, }, + bupt: { + username: envs.BUPT_USERNAME, + password: envs.BUPT_PASSWORD, + }, }; }; calculateValue(); diff --git a/lib/router.js b/lib/router.js index 36d171b14b..d6965d7b47 100644 --- a/lib/router.js +++ b/lib/router.js @@ -1309,6 +1309,7 @@ router.get('/21caijing/channel/:name', require('./routes/21caijing/channel')); // 北京邮电大学 router.get('/bupt/yz/:type', require('./routes/universities/bupt/yz')); router.get('/bupt/grs', require('./routes/universities/bupt/grs')); +router.get('/bupt/portal', require('./routes/universities/bupt/portal')); // VOCUS 方格子 router.get('/vocus/publication/:id', require('./routes/vocus/publication')); diff --git a/lib/routes/universities/bupt/portal.js b/lib/routes/universities/bupt/portal.js new file mode 100644 index 0000000000..a65b2e32f8 --- /dev/null +++ b/lib/routes/universities/bupt/portal.js @@ -0,0 +1,172 @@ +const config = require('@/config').value; +const got = require('@/utils/got'); +const cheerio = require('cheerio'); +const url = require('url'); + +function isToday(time) { + return new Date().getTime() - new Date(time).getTime() < 86400000; +} + +function getPubDate(time) { + return isToday(time) ? new Date() : new Date(time + ' 08:00:00'); +} + +const base = 'http://my.bupt.edu.cn'; +const sourceTimezoneOffset = -8; + +let portalCookie = null; +let authCookie = null; +let castgc = null; + +module.exports = async (ctx) => { + if (!config.bupt || !config.bupt.username || !config.bupt.password) { + throw 'BUPT Portal RSS is disabled due to the lack of relevant config'; + } + + const reqUrl = url.resolve(base, '/index.portal?.pn=p1778'); + let reqRes = await got({ + method: 'get', + followRedirect: false, + url: reqUrl, + headers: { + cookie: portalCookie, + }, + }); + + // Login + if (reqRes.statusCode === 302) { + if (reqRes.headers['set-cookie'] === undefined) { + portalCookie = null; + throw 'Can not obtain portalCookie'; + } + portalCookie = reqRes.headers['set-cookie'].toString().match(/JSESSIONID=.{37}/)[0]; + + const authRes = await got({ + method: 'get', + followRedirect: false, + url: reqRes.headers.location, + headers: { + cookie: `${authCookie}; ${castgc}`, + }, + }); + if (authRes.statusCode === 200) { + authCookie = authRes.headers['set-cookie'].toString().match(/JSESSIONID=.{37}/)[0]; + const authPage = cheerio.load(authRes.data); + + const loginRes = await got({ + method: 'post', + followRedirect: false, + url: reqRes.headers.location, + headers: { + cookie: authCookie, + referer: reqRes.headers.location, + }, + form: true, + data: { + username: config.bupt.username, + password: config.bupt.password, + lt: authPage('[name=lt]').attr('value'), + execution: authPage('[name=execution]').attr('value'), + _eventId: authPage('[name=_eventId]').attr('value'), + rmShown: 1, + }, + }); + if (loginRes.headers['set-cookie'] === undefined) { + authCookie = null; + castgc = null; + throw 'Can not obtain castgc'; + } + castgc = loginRes.headers['set-cookie'].toString().match(/CASTGC=.{85}/)[0]; + + await got({ + method: 'get', + followRedirect: false, + url: loginRes.headers.location, + headers: { + cookie: portalCookie, + }, + }); + } else if (authRes.statusCode === 302) { + await got({ + method: 'get', + followRedirect: false, + url: authRes.headers.location, + headers: { + cookie: portalCookie, + }, + }); + } else { + throw 'BUPT auth login failed'; + } + + reqRes = await got({ + method: 'get', + followRedirect: false, + url: reqUrl, + headers: { + cookie: portalCookie, + }, + }); + } + + const $ = cheerio.load(reqRes.data); + const list = $('.newslist li').get(); + const out = await Promise.all( + list.map(async (i) => { + const item = $(i); + const itemUrl = url.resolve( + base, + $(item) + .find('a') + .attr('href') + ); + const cache = await ctx.cache.get(itemUrl); + if (cache) { + return Promise.resolve(JSON.parse(cache)); + } + + const title = $(item) + .find('a') + .text(); + const author = $(item) + .find('.author') + .text(); + const time = getPubDate( + $(item) + .find('.time') + .text() + ); + time.setTime(time.getTime() + (sourceTimezoneOffset - time.getTimezoneOffset() / 60) * 60 * 60 * 1000); + + const itemResponse = await got({ + method: 'get', + url: itemUrl, + headers: { + cookie: portalCookie, + }, + }); + const itemElement = cheerio.load(itemResponse.data); + // Remove useless print button + itemElement('table.Noprint').remove(); + const description = itemElement('.singleexpert').html(); + + const single = { + title: title, + author: author, + description: description, + pubDate: time.toUTCString(), + link: itemUrl, + guid: itemUrl, + }; + + ctx.cache.set(itemUrl, JSON.stringify(single)); + return Promise.resolve(single); + }) + ); + + ctx.state.data = { + title: '北京邮电大学信息门户', + link: reqUrl, + item: out, + }; +};