feat(route): add china silk museum exhibit route (#22809)

* feat(route): add china silk museum exhibit route

* fix: update radar source

* fix: update rawSrc to remove redundancy

* fix: update according to part of bot review
This commit is contained in:
Jiamin 2026-07-25 17:24:59 +08:00 committed by GitHub
parent 0103920744
commit 8a2128988a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 129 additions and 0 deletions

View File

@ -0,0 +1,10 @@
import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'China National Silk Museum',
url: 'www.chinasilkmuseum.com',
zh: {
name: '中国丝绸博物馆',
},
};

View File

@ -0,0 +1,119 @@
import { load } from 'cheerio';
import dayjs from 'dayjs';
import { renderToString } from 'hono/jsx/dom/server';
import type { Data, DataItem, Route } from '@/types';
import cache from '@/utils/cache';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import { namespace } from './namespace';
// Convert YYYY年M月D日 to YYYY-MM-DD
const fmtExhibitionDate = (raw: string | undefined) => {
if (!raw) {
return;
}
const d = dayjs(raw.trim(), 'YYYY年M月D日');
return d.isValid() ? d.format('YYYY-MM-DD') : undefined;
};
// used for 2026年6月24日 - 2026年9月1日
const parseExhibitionDuration = (fullDuration: string): { startDate: string | undefined; endDate: string | undefined } => {
const [startRaw, endRaw] = fullDuration.split(' - ', 2);
return { startDate: fmtExhibitionDate(startRaw), endDate: fmtExhibitionDate(endRaw) };
};
export const route: Route = {
path: '/zz',
categories: ['travel'],
example: '/chinasilkmuseum/zz',
name: 'Exhibition',
maintainers: ['magazian'],
radar: [
{
source: ['www.chinasilkmuseum.com/zz/list_17.aspx'],
target: '/zz',
},
],
handler: async () => {
const baseUrl = 'https://www.chinasilkmuseum.com';
const listUrl = `${baseUrl}/zz/list_17.aspx`;
const museumName = namespace.zh?.name ?? namespace.name;
const response = await ofetch(listUrl);
const $ = load(response);
const items = $('div.about_body div.show_info ul.ul li')
.toArray()
.map((el) => {
const $el = $(el);
const $titleLink = $el.find('div.show_text h3.h3 a');
const title = $titleLink.text();
const rawHref = $titleLink.attr('href') ?? '';
const link = new URL(rawHref, baseUrl).href;
const rawSrc = $el.find('a > img').attr('src') ?? '';
const imgUrl = new URL(rawSrc, baseUrl).href;
return { title, link, imgUrl };
});
const list = await Promise.all(
items.map((item) =>
cache.tryGet(item.link, async () => {
const detailRes = await ofetch(item.link);
const $d = load(detailRes);
const location = $d('div.detail_text p').first().text().replace('展览地点:', '').trim();
const fullDuration = $d('div.detail_text p').eq(1).text().replace('展览时间:', '').trim();
const { startDate, endDate } = parseExhibitionDuration(fullDuration);
const pubDate = startDate ? parseDate(startDate) : undefined;
const description = renderToString(
<div>
<img src={item.imgUrl} />
<br />
<p>
<b></b>
{location || '参考详情'}
</p>
<p>
<b></b>
{startDate || '未定/常设'}
</p>
<p>
<b></b>
{endDate || '未定/常设'}
</p>
{fullDuration && (
<p>
<small>{fullDuration}</small>
</p>
)}
</div>
);
return {
title: item.title,
link: item.link,
pubDate,
description,
_extra: {
museumName,
location,
startDate,
endDate,
},
} as DataItem;
})
)
);
return {
title: `${museumName} - 在展`,
link: listUrl,
language: 'zh-CN',
item: list,
} as Data;
},
};