diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-11-05 13:40:32 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-11-05 13:40:32 +0900 |
| commit | ff9bafe77a5e0dd84b613bb2aae0bf2a9cb84359 (patch) | |
| tree | e86e638f80f94f42e67253eb27f0a8d36f4fe2d8 /src/pages/kodaira/events/feed.xml.ts | |
| parent | 881485f010d6d576bab958548e3c955b8067dd34 (diff) | |
events: 年度別アーカイブとRSSフィードを追加
月別ページに加えて、4月〜翌3月の年度ページ(/kodaira/events/r8/)を
追加した。月別リンクは年度ごとにまとめ、今後の予定はfeed.xmlで
配信する。
Diffstat (limited to 'src/pages/kodaira/events/feed.xml.ts')
| -rw-r--r-- | src/pages/kodaira/events/feed.xml.ts | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/src/pages/kodaira/events/feed.xml.ts b/src/pages/kodaira/events/feed.xml.ts new file mode 100644 index 0000000..860f5f2 --- /dev/null +++ b/src/pages/kodaira/events/feed.xml.ts @@ -0,0 +1,79 @@ +// これからの予定のRSSフィード(/kodaira/events/feed.xml)。 +// ビルド時点の日付以降に開催される予定を、開催日の早い順に最大60件出力する。 +import type { APIRoute } from 'astro'; +import { items } from '@/lib/schedule-months'; +import { entryDates } from '@/lib/schedule-dates'; +import { SCHEDULE_BASE_PATH, SITE_URL } from '@/data/schedule/config'; + +const stripTags = (value: string) => + value + .replace(/<[^>]*>/g, '') + .replace(/ /g, ' ') + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/\s+/g, ' ') + .trim(); + +const escapeXml = (value: string) => + value + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + +const pad = (n: number) => String(n).padStart(2, '0'); +const now = new Date(); +const todayKey = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`; + +const toRfc822 = (date: string) => new Date(`${date}T00:00:00+09:00`).toUTCString(); + +const entries = items + .map((item) => { + const dates = entryDates(item.type, item.row.date, item.row.place); + return { item, upcoming: dates.filter((date) => date >= todayKey) }; + }) + .filter(({ upcoming }) => upcoming.length > 0) + .sort((a, b) => a.upcoming[0].localeCompare(b.upcoming[0])) + .slice(0, 60); + +const itemXml = ({ item: { type, row }, upcoming }: (typeof entries)[number]) => { + const title = stripTags(row.title); + const description = [stripTags(row.place), stripTags(row.note)].filter(Boolean).join('/'); + const datesLabel = upcoming + .map((date) => `${Number(date.slice(5, 7))}月${Number(date.slice(8, 10))}日`) + .join('、'); + const link = `${SITE_URL}${SCHEDULE_BASE_PATH}${upcoming[0].slice(0, 7)}/`; + const guid = `${SITE_URL}${SCHEDULE_BASE_PATH}#${type}-${upcoming[0]}-${encodeURIComponent(title)}`; + + return ` <item> + <title>${escapeXml(title)}</title> + <link>${escapeXml(link)}</link> + <guid isPermaLink="false">${escapeXml(guid)}</guid> + <category>${type === 'meeting' ? '審議会等・市議会' : 'イベント'}</category> + <pubDate>${toRfc822(upcoming[0])}</pubDate> + <description>${escapeXml(`開催日:${datesLabel}${description ? `/${description}` : ''}`)}</description> + </item>`; +}; + +export const GET: APIRoute = () => { + const xml = `<?xml version="1.0" encoding="UTF-8"?> +<rss version="2.0"> + <channel> + <title>小平市のイベント・行事予定(審議会・市議会の日程も掲載)</title> + <link>${SITE_URL}${SCHEDULE_BASE_PATH}</link> + <description>小平市のイベントと、審議会等・市議会の開催予定です。市報こだいら・市公式X・LoGoフォーム・市議会の行事予定表などをもとにまとめています。</description> + <language>ja</language> + <lastBuildDate>${now.toUTCString()}</lastBuildDate> +${entries.map(itemXml).join('\n')} + </channel> +</rss> +`; + + return new Response(xml, { + headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' }, + }); +}; |
