diff --git a/lib/routes/whu/rsgis.ts b/lib/routes/whu/rsgis.ts new file mode 100644 index 000000000..3eaaff966 --- /dev/null +++ b/lib/routes/whu/rsgis.ts @@ -0,0 +1,277 @@ +import { Route, DataItem } from '@/types'; +import ofetch from '@/utils/ofetch'; +import { load, Cheerio, AnyNode } from 'cheerio'; +import { parseDate } from '@/utils/parse-date'; +import timezone from '@/utils/timezone'; +import { Context } from 'hono'; +import cache from '@/utils/cache'; + +interface Post extends DataItem { + external: boolean; +} + +const baseUrl = 'https://rsgis.whu.edu.cn'; +const categoryMap = { + index: { + name: '首页', + path: '', + }, + xyxw: { + name: '学院新闻', + path: 'xyxw1', + sub: { + xyyw: { + name: '学院要闻', + path: 'xyyw2', + }, + hzjl: { + name: '合作交流', + path: 'hzjl', + }, + mtjj: { + name: '媒体聚焦', + path: 'mtjj', + }, + xgyw: { + name: '学工要闻', + path: 'xgyw', + }, + }, + }, + kxyj: { + name: '科学研究', + path: 'kxyj', + sub: { + xsbg: { + name: '学术报告', + path: 'xsbg', + }, + xsjl: { + name: '学术交流', + path: 'xsjl', + }, + kycg: { + name: '学术成果', + path: 'kycg', + }, + sbxx: { + name: '申报信息', + path: 'sbxx', + }, + }, + }, + tzgg: { + name: '通知公告', + path: 'tzgg1', + sub: { + xytz: { + name: '学院通知', + path: 'xytz', + }, + jxdt: { + name: '教学动态', + path: 'jxdt', + }, + xsdt: { + name: '学术动态', + path: 'xsdt', + }, + rcyj: { + name: '人才引进', + path: 'rcyj', + }, + }, + }, +}; + +/** + * Check whether the link is external. + * + * @param link Post link + * @returns Whether or not weixin post + */ +function checkExternal(link: string): boolean { + const matchWeixin = link.match(/^((http:\/\/)|(https:\/\/))?([\dA-Za-z]([\dA-Za-z-]{0,61}[\dA-Za-z])?\.)+[A-Za-z]{2,6}(\/)/); + return !!matchWeixin?.length; +} + +/** + * Get information from a list of paired link and date. + * + * @param element + * @returns A list of RSS meta node. + */ +function parseListLinkDateItem(element: Cheerio, currentUrl: string) { + const linkElement = element.find('a').first(); + const title = linkElement.text(); + const href = linkElement.attr('href'); + if (href === undefined) { + throw new Error('Cannot get link'); + } + const external = checkExternal(href); + const link = external ? href : new URL(href, currentUrl).href; + const pubDate = element.find('div.date1').first().text(); + return { + title, + link, + pubDate: timezone(parseDate(pubDate, 'YYYY-MM-DD'), +8), + description: title, + external, + }; +} + +async function getDetail(item: Post): Promise { + const link = item.link; + return link + ? await cache.tryGet(`whu:rsgis:${link}`, async () => { + if (item.external) { + item.description = `阅读原文`; + } else { + const response = await ofetch(link); + const $ = load(response); + const title = $('div.content div.content_title h1').first().text(); + const content = $('div.content div.v_news_content').first().html(); + item.title = title; + item.description = content || ''; + } + return item; + }) + : item; +} + +/** + * Process index type. + */ +async function handleIndex(): Promise> { + const url = `${baseUrl}/index.htm`; + const response = await ofetch(url); + const $ = load(response); + // 学院新闻 + const xyxwList: Array = $('div.main1 > div.newspaper:nth-child(1) > div.newspaper_list > ul > li') + .toArray() + .map((item) => parseListLinkDateItem($(item), baseUrl)); + // 通知公告 + const tzggList: Array = $('div.main1 > div.newspaper:nth-child(2) > div.newspaper_list > ul > li') + .toArray() + .map((item) => parseListLinkDateItem($(item), baseUrl)); + // 学术动态 + const xsdtList: Array = $('div.main3 div.inner > div.newspaper:nth-child(1) > ul.newspaper_list2 > li:nth-child(1) > ul > li') + .toArray() + .map((item) => parseListLinkDateItem($(item), baseUrl)); + // 学术进展 + const xsjzList: Array = $('div.main3 div.inner > div.newspaper:nth-child(1) > ul.newspaper_list2 > li:nth-child(2) > ul > li') + .toArray() + .map((item) => parseListLinkDateItem($(item), baseUrl)); + // 教学动态 + const jxdtList: Array = $('div.main3 div.inner > div.newspaper:nth-child(2) > div.newspaper_list2 > ul > li') + .toArray() + .map((item) => parseListLinkDateItem($(item), baseUrl)); + // 学工动态 + const xgdtList: Array = $('div.main3 div.inner > div.newspaper:nth-child(3) > div.newspaper_list2 > ul > li') + .toArray() + .map((item) => parseListLinkDateItem($(item), baseUrl)); + // 组合所有新闻 + const fullList = await Promise.all([...xyxwList, ...tzggList, ...xsdtList, ...xsjzList, ...jxdtList, ...xgdtList].map(async (item) => await getDetail(item))); + return fullList; +} + +/** + * Process non-index types. + * + * @param type Level 1 type + * @param sub Level 2 type + */ +async function handlePostList(type: string, sub: string): Promise> { + let urlList: Array<{ url: string; base: string }> = []; + const category = categoryMap[type]; + if (sub === 'all') { + const subMap = category.sub; + urlList = Object.keys(subMap).map((key) => { + const subType = subMap[key]; + return { + url: `${baseUrl}/${category.path}/${subType.path}.htm`, + base: `${baseUrl}/${category.path}`, + }; + }); + } else if (sub in category.sub) { + urlList.push({ + url: `${baseUrl}/${category.path}/${category.sub[sub].path}.htm`, + base: `${baseUrl}/${category.path}`, + }); + } else { + throw new Error('No such sub type.'); + } + const urlPosts = await Promise.all( + urlList.map(async (url) => { + const response = await ofetch(url.url); + const $ = load(response); + return $('div.neiinner > div.nav_right > div.right_inner > div.list > ul > li') + .toArray() + .map((item) => parseListLinkDateItem($(item), url.base)); + }) + ); + const fullList = await Promise.all(urlPosts.flat().map(async (item) => await getDetail(item))); + return fullList; +} + +export const route: Route = { + path: '/rsgis/:type/:sub?', + categories: ['university'], + example: '/whu/rsgis/index', + parameters: { + type: '栏目,详见表格', + sub: '子栏目。当 `type` 为 `index` 的时候不起效;当 `type` 为其他合法值时,默认为 `all` 表示所有子类,其他可选项见下表。', + }, + radar: [ + { + source: ['rsgis.whu.edu.cn/index.htm'], + target: '/rsgis/index', + }, + ], + name: '武汉大学遥感信息工程学院', + maintainers: ['HPDell'], + description: ` + +| 分类名 | \`type\` 值 | 子类名 | \`sub\` 值 | +| :------: | :-------- | :------: | :------- | +| 首页 | \`index\` | | | +| 学院新闻 | \`xyxw\` | 全部 | \`all\` | +| | | 学院要闻 | \`xyyw\` | +| | | 合作交流 | \`hzjl\` | +| | | 媒体聚焦 | \`mtjj\` | +| | | 学工要闻 | \`xgyw\` | +| 科学研究 | \`kxyj\` | 全部 | \`all\` | +| | | 学术报告 | \`xsbg\` | +| | | 学术交流 | \`xsjl\` | +| | | 学术成果 | \`kycg\` | +| | | 申报信息 | \`sbxx\` | +| 通知公告 | \`tzgg\` | 全部 | \`all\` | +| | | 学院通知 | \`xytz\` | +| | | 教学动态 | \`jxdt\` | +| | | 学术动态 | \`xsdt\` | +| | | 人才引进 | \`rcyj\` | +`, + handler: async (ctx: Context) => { + const { type = 'index', sub = 'all' } = ctx.req.param(); + let itemList: Array = []; + switch (type) { + case 'index': + itemList = await handleIndex(); + break; + case 'xyxw': + case 'kxyj': + case 'tzgg': + itemList = await handlePostList(type, sub); + break; + default: + throw new Error('No such type'); + } + + return { + title: `${categoryMap[type].name} - 武汉大学遥感信息工程学院`, + link: baseUrl, + description: `${categoryMap[type].name} - 武汉大学遥感信息工程学院`, + item: itemList, + }; + }, +};