Skip to content

Commit 74cb1fa

Browse files
authored
feat(route): add Nanjing Museum exhibition route (#22261)
* feat(route): add Nanjing Museum exhibition route * fix: update the startDate and endDate * fix: remove redundant fetchTitle * Update lib/routes/njmuseum/exhibitionindex.tsx
1 parent e171994 commit 74cb1fa

2 files changed

Lines changed: 176 additions & 0 deletions

File tree

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { renderToString } from 'hono/jsx/dom/server';
2+
3+
import type { Route } from '@/types';
4+
import got from '@/utils/got';
5+
import { parseDate } from '@/utils/parse-date';
6+
7+
import { namespace } from './namespace';
8+
9+
// format the date to YYYY-MM-DD and handle missing year or month
10+
const extractDates = (durationStr: string) => {
11+
let startDate: string | undefined;
12+
let endDate: string | undefined;
13+
14+
if (!durationStr) {
15+
return { startDate, endDate };
16+
}
17+
18+
const parts = durationStr.split(/-+/).map((p) => p.trim()); // currently - is used
19+
const startStr = parts[0];
20+
const endStr = parts[1];
21+
22+
let startYear: string | undefined;
23+
let startMonth: string | undefined;
24+
25+
const startRegex = /(\d{4})[.](\d{1,2})(?:[.](\d{1,2}))?/; // matches formats like "2024年5月10日", "2024.5.10"
26+
const startMatch = startStr.match(startRegex);
27+
28+
if (startMatch) {
29+
startYear = startMatch[1];
30+
startMonth = startMatch[2].padStart(2, '0');
31+
const startDay = startMatch[3] ? startMatch[3].padStart(2, '0') : '01'; // use 1st day of month if day is missing
32+
startDate = `${startYear}-${startMonth}-${startDay}`;
33+
}
34+
35+
if (endStr && startDate) {
36+
const endRegex = /(?:(\d{4})[.])?(\d{1,2})(?:[.](\d{1,2}))?/;
37+
const endMatch = endStr.match(endRegex);
38+
39+
if (endMatch) {
40+
const matchYear = endMatch[1];
41+
const matchMonth: string | undefined = endMatch[2];
42+
const matchDay: string | undefined = endMatch[3];
43+
44+
const finalEndYear = matchYear || startYear;
45+
const finalEndMonth = matchMonth ? matchMonth.padStart(2, '0') : startMonth;
46+
const finalEndDay = matchDay ? matchDay.padStart(2, '0') : '01';
47+
endDate = `${finalEndYear}-${finalEndMonth}-${finalEndDay}`;
48+
}
49+
}
50+
51+
return { startDate, endDate };
52+
};
53+
54+
interface ExhibitType {
55+
modular: string;
56+
code: string;
57+
value: string;
58+
pageSize?: number;
59+
pageNum?: number;
60+
isList?: boolean;
61+
}
62+
63+
const TYPE_MAP: Record<string, ExhibitType> = {
64+
review: { modular: 'exIndexReview', code: 'properties', value: '5' },
65+
abroad: { modular: 'exIndexGoAbroad', code: 'properties', value: '7' },
66+
virtual: { modular: 'exIndexVirtual', code: 'properties', value: '11' },
67+
forecast: { modular: 'exIndexForecast', code: 'properties', value: '1' },
68+
default: { modular: 'exIndex', code: '', value: '0', pageSize: 6, pageNum: 1, isList: true },
69+
};
70+
71+
export const route: Route = {
72+
path: '/exhibitionIndex/:type?',
73+
categories: ['travel'],
74+
example: '/njmuseum/exhibitionIndex/review',
75+
parameters: {
76+
type: 'Exhibition type, supported values: review (展览回顾) | abroad (赴外展览) | virtual (虚拟展厅) | forecast (展览预告). Default: Current Exhibitions (正在展出).',
77+
},
78+
name: 'Exhibitions',
79+
maintainers: ['magazian'],
80+
radar: [
81+
{
82+
source: ['www.njmuseum.com/zh/exhibitionIndex'],
83+
target: '/exhibitionIndex',
84+
},
85+
],
86+
handler: async (ctx) => {
87+
const typeParam = ctx.req.param('type') || 'default';
88+
const currentType = TYPE_MAP[typeParam];
89+
90+
const baseUrl = 'https://www.njmuseum.com';
91+
const resourceUrl = 'https://manage.njmuseum.org.cn'; // use for imgUrl
92+
93+
const apiUrl = `${baseUrl}/api/exhibition/list`;
94+
95+
const museumName = namespace.zh?.name || namespace.name;
96+
97+
const fetchExhibits = async (config: ExhibitType) => {
98+
const response = await got.post(apiUrl, {
99+
form: config,
100+
});
101+
102+
const data = response.data?.data;
103+
104+
return {
105+
list: data.list as Array<Record<string, any>>,
106+
title: data.title as string,
107+
};
108+
};
109+
110+
const { list: rawItems, title: titleTag } = await fetchExhibits(currentType);
111+
112+
const items = rawItems?.map((item) => {
113+
const title = item.title;
114+
const itemLink = `${baseUrl}/zh/${item.link}?id=${item.id}`;
115+
const imgPath = item.imgSrc?.[0];
116+
const imgUrl = `${resourceUrl}${imgPath}`;
117+
const location = item.position;
118+
const fullDuration = item.timedesc;
119+
const { startDate, endDate } = extractDates(fullDuration);
120+
const pubDate = startDate ? parseDate(startDate) : undefined; // use start date as publication date if publication date is notavailable
121+
122+
const description = renderToString(
123+
<div>
124+
<img src={imgUrl} />
125+
<br />
126+
<p>
127+
<b>地点:</b>
128+
{location || '参考展览详情'}
129+
</p>
130+
<p>
131+
<b>开展:</b>
132+
{startDate ?? '未定/常设'}
133+
</p>
134+
<p>
135+
<b>闭展:</b>
136+
{endDate ?? '未定/常设'}
137+
</p>
138+
<p>
139+
<small>原始展期:{fullDuration}</small>
140+
</p>
141+
</div>
142+
);
143+
144+
return {
145+
title,
146+
link: itemLink,
147+
pubDate,
148+
description,
149+
// For further .ics file processing
150+
_extra: {
151+
museumName,
152+
location,
153+
startDate,
154+
endDate,
155+
},
156+
};
157+
});
158+
159+
return {
160+
title: `${museumName} - 展览 - ${titleTag}`,
161+
link: `${baseUrl}/zh/exhibitionIndex`,
162+
language: 'zh-CN',
163+
item: items,
164+
};
165+
},
166+
};

lib/routes/njmuseum/namespace.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { Namespace } from '@/types';
2+
3+
export const namespace: Namespace = {
4+
name: 'Nanjing Museum',
5+
url: 'www.njmuseum.com/zh',
6+
7+
zh: {
8+
name: '南京博物院',
9+
},
10+
};

0 commit comments

Comments
 (0)