aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/pages/kodaira/events/feed.xml.ts
diff options
context:
space:
mode:
authorYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-11-05 13:40:32 +0900
committerYasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com>2026-11-05 13:40:32 +0900
commitff9bafe77a5e0dd84b613bb2aae0bf2a9cb84359 (patch)
treee86e638f80f94f42e67253eb27f0a8d36f4fe2d8 /src/pages/kodaira/events/feed.xml.ts
parent881485f010d6d576bab958548e3c955b8067dd34 (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.ts79
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(/&nbsp;/g, ' ')
+ .replace(/&amp;/g, '&')
+ .replace(/&lt;/g, '<')
+ .replace(/&gt;/g, '>')
+ .replace(/&quot;/g, '"')
+ .replace(/&#39;/g, "'")
+ .replace(/\s+/g, ' ')
+ .trim();
+
+const escapeXml = (value: string) =>
+ value
+ .replace(/&/g, '&amp;')
+ .replace(/</g, '&lt;')
+ .replace(/>/g, '&gt;')
+ .replace(/"/g, '&quot;')
+ .replace(/'/g, '&apos;');
+
+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' },
+ });
+};