feat: add Shanghai Museum exhibition route (#21890)

* feat: add Shanghai Museum exhibition route

* Update shanghaimuseum.ts

* feat: add Shanghai Museum exhibition route

* Update shanghaimuseum.ts

* Update shanghaimuseum.ts

* docs: add namespace for shanghaimuseum

* feat(route): rename to offline-exhibit and update metadata

* fix: update with fixed output and fix errors

* fix: fix errors

* fix: add exhibitiontype

* fix: refactor to JSX

* feat: update shanghai museum route with type param

* fix: update offline-exhibit to use path params

* fix: exhibitItem from ExhibitItem

* fix: update interface
This commit is contained in:
Jiamin 2026-05-14 22:25:36 +08:00 committed by GitHub
parent 0f164eb348
commit 5cb99133d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 152 additions and 0 deletions

View File

@ -0,0 +1,10 @@
import type { Namespace } from '@/types';
export const namespace: Namespace = {
name: 'Shanghai Museum',
url: 'www.shanghaimuseum.net',
zh: {
name: '上海博物馆',
},
};

View File

@ -0,0 +1,142 @@
import { renderToString } from 'hono/jsx/dom/server';
import type { Route } from '@/types';
import got from '@/utils/got';
import { parseDate } from '@/utils/parse-date';
import { namespace } from './namespace';
interface ExhibitItem {
code: string;
exhibitDateRange?: string;
exhibitPlace?: string;
issueTime: string;
name: string;
picPath?: string;
}
export const route: Route = {
path: '/display/offline-exhibit/:type?',
categories: ['travel'],
example: '/shanghaimuseum/display/offline-exhibit/PRESENT',
parameters: {
type: 'Exhibition type, supported values: PRESENT (当期展览) | PAST (往期展览). Default: All exhibitions (both PRESENT and PAST).',
},
// Use SHM English version channel name
name: 'Special Exhibitions',
maintainers: ['magazian'],
radar: [
{
source: ['www.shanghaimuseum.net/mu/frontend/pg/display/offline-exhibit'],
target: '/display/offline-exhibit',
},
],
handler: async (ctx) => {
const type = ctx.req.param('type')?.toUpperCase();
const baseUrl = 'https://www.shanghaimuseum.net';
const apiUrl = `${baseUrl}/mu/frontend/pg/display/search-exhibit`;
const fetchExhibits = async (status: string, limit = 20): Promise<ExhibitItem[]> => {
const response = await got.post(apiUrl, {
json: {
limit,
page: 1,
params: {
exhibitTypeCode: 'OFFLINE_EXHIBITION',
langCode: 'CHINESE',
offlineExhibitionType: status,
},
},
});
return (response.data?.data as ExhibitItem[]) || [];
};
let rawItems: ExhibitItem[] = [];
let titleTag = '';
if (type === 'PAST' || type === 'PRESENT') {
// if user specified the type with past or present
rawItems = await fetchExhibits(type, 50);
titleTag = type === 'PAST' ? '往期展览' : '当期展览';
} else {
// default to fetch both present and past exhibitions
const [presentItems, pastItems] = await Promise.all([fetchExhibits('PRESENT', 50), fetchExhibits('PAST', 20)]);
rawItems = [...presentItems, ...pastItems];
titleTag = '当期展览&往期展览';
}
const museumName = namespace.zh?.name || namespace.name;
const items = rawItems.map((item) => {
const title = item.name;
const itemLink = `${baseUrl}/mu/frontend/pg/article/id/${item.code}`;
const imgUrl = item.picPath ? `${baseUrl}/${item.picPath}` : '';
const location = item.exhibitPlace || '上海博物馆';
const pubDate = parseDate(item.issueTime);
const fullDuration = item.exhibitDateRange || '';
const [startDate, endDate] = fullDuration.includes(' - ') ? fullDuration.split(' - ').map((s) => s.trim()) : [fullDuration, ''];
const description = renderToString(
<div>
{imgUrl && (
<>
<img src={imgUrl} />
<br />
</>
)}
<p>
<b></b>
{location}
</p>
<p>
<b></b>
{startDate ?? '未定/常设'}
</p>
<p>
<b></b>
{endDate ?? '未定/常设'}
</p>
{fullDuration && (
<p>
<small>
{fullDuration.split('\n').map((line, i) => (
<span key={i}>
{line}
<br />
</span>
))}
</small>
</p>
)}
</div>
);
return {
title,
link: itemLink,
pubDate,
description,
// For further .ics file processing
_extra: {
museumName,
title,
location,
startDate, // format: YYYY-MM-DD or '未定/常设'
endDate, // format: YYYY-MM-DD or '未定/常设'
itemLink,
},
};
});
return {
title: `${museumName} - 特别展览 - ${titleTag}`,
link: `${baseUrl}/mu/frontend/pg/display/offline-exhibit`,
language: 'zh-CN',
item: items,
};
},
};