diff --git a/lib/routes/minhangmuseum/interim.tsx b/lib/routes/minhangmuseum/interim.tsx new file mode 100644 index 000000000000..50ba3c524893 --- /dev/null +++ b/lib/routes/minhangmuseum/interim.tsx @@ -0,0 +1,125 @@ +import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; + +import type { DataItem, Route } from '@/types'; +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { parseDate } from '@/utils/parse-date'; + +import { namespace } from './namespace'; + +const parseExhibitionDuration = (duration?: string) => { + const [startDate, endDate] = duration?.match(/\d{4}年\d{1,2}月\d{1,2}日/g)?.map((d) => d.replace(/(\d{4})年(\d{1,2})月(\d{1,2})日/, (_, y, m, d) => `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`)) ?? []; + + return { + startDate, + endDate, + }; +}; + +export const route: Route = { + path: '/interim', + categories: ['travel'], + example: '/minhangmuseum/interim', + name: '临时展览', + maintainers: ['magazian'], + radar: [ + { + source: ['minhangmuseum.shmh.gov.cn/weixin/interim/list.htm'], + target: '/interim', + }, + ], + handler: async () => { + const baseUrl = 'https://minhangmuseum.shmh.gov.cn'; + const listUrl = `${baseUrl}/weixin/interim/list.htm`; + const museumName = namespace.zh?.name || namespace.name; + + const response = await ofetch(listUrl); + const $ = load(response); + + const listItems = $('#courseContent li, .ex-ul li') + .toArray() + .map((el) => { + const $el = $(el); + const href = $el.find('a').attr('href'); + if (!href) { + return null; + } + const itemLink = new URL(href, baseUrl).href; + const title = $el.find('.ex-li-title').text(); + const fullDuration = $el.find('.apply-time').text(); + const imgUrl = $el.find('.ex-li-img img').attr('src'); + + return { + title, + itemLink, + fullDuration, + imgUrl, + }; + }) + .filter((item): item is NonNullable => item !== null); + + const items: DataItem[] = await Promise.all( + listItems.map((item) => + cache.tryGet(item.itemLink, async () => { + const detailResponse = await ofetch(item.itemLink); + const $detail = load(detailResponse); + + let location: string | undefined; + $detail('.activity-detail-time ul li').each((_, el) => { + const label = $detail(el).find('.detail-time-lf').text(); + const val = $detail(el).find('.detail-time-rig').text(); + if (label.includes('地点') && val) { + location = val; + } + }); + + const { startDate, endDate } = parseExhibitionDuration(item.fullDuration); + const pubDate = startDate ? parseDate(startDate) : undefined; + + const description = renderToString( +
+ +
+

+ 地点: + {location || '参考详情'} +

+

+ 开展: + {startDate || '未定/常设'} +

+

+ 闭展: + {endDate || '未定/常设'} +

+

+ 原始展期:{item.fullDuration} +

+
+ ); + + return { + title: item.title, + link: item.itemLink, + pubDate, + description, + _extra: { + museumName, + location, + startDate, + endDate, + }, + }; + }) + ) + ); + + return { + title: `${museumName} - 临时展览`, + link: listUrl, + language: 'zh-CN', + item: items, + }; + }, +}; diff --git a/lib/routes/minhangmuseum/namespace.ts b/lib/routes/minhangmuseum/namespace.ts new file mode 100644 index 000000000000..7ffe99144141 --- /dev/null +++ b/lib/routes/minhangmuseum/namespace.ts @@ -0,0 +1,10 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'Minhang Museum', + url: 'minhangmuseum.shmh.gov.cn', + + zh: { + name: '上海市闵行区博物馆', + }, +};