diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-11-02 09:01:49 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-11-02 09:01:49 +0900 |
| commit | 9f2147a8d34015c841668cbbb34427ae969d0b52 (patch) | |
| tree | 0f7f9c658ca14979678cdd6445751b9e7cad0f7a /scripts/update-library-dates.mjs | |
| parent | 5c6a79f7869b0da7a03be265686fc35ea110b0e0 (diff) | |
hp-archive: 図書館のイベント掲載と絞り込みを追加
- 小平市立図書館の月間カレンダーから、おはなし会・絵本のへやなど16件を掲載
- 「図書館」カテゴリボタンを追加し、カレンダー下部に絞り込み中の表示を追加
- 11月5日の公共施設マネジメント調査特別委員会を視察(傍聴なし)に修正
- 図書館イベントの日付更新スクリプトと、X投稿用のキャプチャスクリプトを追加
Diffstat (limited to 'scripts/update-library-dates.mjs')
| -rw-r--r-- | scripts/update-library-dates.mjs | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/scripts/update-library-dates.mjs b/scripts/update-library-dates.mjs new file mode 100644 index 0000000..2317b17 --- /dev/null +++ b/scripts/update-library-dates.mjs @@ -0,0 +1,165 @@ +#!/usr/bin/env node +// 小平市立図書館のイベントカレンダーから開催日を取得し、 +// 行事予定ページの図書館イベント行(日付セル)を更新する。 +// +// 使い方: +// node scripts/update-library-dates.mjs … 変更内容を表示(書き換えない) +// node scripts/update-library-dates.mjs --apply … 実際に書き換える +// node scripts/update-library-dates.mjs --months 4 … 当月から4か月分を取得(既定3) +// +// 行の追加・削除は自動では行わない(内容を確認して手で編集する)。 +// 表にない新しいイベント、カレンダーから消えたイベントは一覧で表示する。 + +import fs from 'node:fs'; +import path from 'node:path'; + +const BASE = 'https://library.kodaira.ed.jp/viewer/'; +const UA = + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'; + +const args = process.argv.slice(2); +const apply = args.includes('--apply'); +const monthsArg = args.indexOf('--months'); +const months = monthsArg >= 0 ? Number(args[monthsArg + 1]) || 3 : 3; +const targetIdx = args.indexOf('--target'); +const targetPath = + targetIdx >= 0 + ? args[targetIdx + 1] + : path.join('src', 'content', 'docs', 'kodaira-data', 'hp-archive', 'schedule.mdx'); + +async function fetchText(url) { + const res = await fetch(url, { headers: { 'User-Agent': UA } }); + if (!res.ok) throw new Error(`${res.status} ${url}`); + return res.text(); +} + +function normalize(html) { + return html.replace(/\s+/g, ' '); +} + +// カレンダーから { id: { title, lib, dates:Set } } を作る +async function collectEvents() { + const now = new Date(); + const events = new Map(); + + for (let i = 0; i < months; i += 1) { + const d = new Date(now.getFullYear(), now.getMonth() + i, 1); + const url = `${BASE}calendar-monthly.html?date=${d.getFullYear()}/${d.getMonth() + 1}/1`; + const html = normalize(await fetchText(url)); + const rows = html.match(/<tr class="calendar-daily cal_event">.*?<\/tr>/g) ?? []; + + for (const row of rows) { + const dm = row.match(/calendar-daily\.html\?date=(\d{4})\/(\d{2})\/(\d{2})/); + if (!dm) continue; + const key = `${dm[1]}-${dm[2]}-${dm[3]}`; + const items = + row.match( + /<a target="_self" href="([^"]*)" class="cal_event_index_\d+">([^<]+)<\/a> <span class="icon[^"]*">([^<]*)<\/span>/g + ) ?? []; + + for (const item of items) { + const m = item.match( + /href="([^"]*)" class="cal_event_index_\d+">([^<]+)<\/a> <span class="icon[^"]*">([^<]*)<\/span>/ + ); + if (!m) continue; + const id = (m[1].match(/id=(\d+)/) ?? [])[1]; + if (!id) continue; + if (!events.has(id)) { + events.set(id, { id, title: m[2].trim(), lib: m[3].trim(), dates: new Set() }); + } + events.get(id).dates.add(key); + } + } + } + return events; +} + +// "2026-11-01" の集合を「11月1日・18日、12月6日・16日」の形にする +// 日付が1つだけのときは「11月19日(木)」のように曜日を付ける +function formatDates(dateKeys, withWeekday) { + const sorted = [...dateKeys].sort(); + const [y0, m0, d0] = sorted[0].split('-').map(Number); + if (withWeekday && sorted.length === 1) { + const w = '日月火水木金土'[new Date(y0, m0 - 1, d0).getDay()]; + return `${m0}月${d0}日(${w})`; + } + const byMonth = new Map(); + for (const key of sorted) { + const [y, m, d] = key.split('-').map(Number); + const k = `${y}-${m}`; + if (!byMonth.has(k)) byMonth.set(k, []); + byMonth.get(k).push(d); + } + const parts = []; + for (const [k, days] of byMonth) { + const m = Number(k.split('-')[1]); + parts.push(`${m}月${days[0]}日${days.slice(1).map((d) => `・${d}日`).join('')}`); + } + return parts.join('、'); +} + +// 図書館のページ(info.html?id=…)を出典に含む行を対象にする。 +// 市報など他の出典と併記している行(例:「市報こだいら10月20日号3面/[図書館のページ](…)」)も拾う。 +const ROW_RE = + /^\| ([^|]*) \| ([^|]*) \| ([^|]*) \| ([^|]*) \| ([^|]*library\.kodaira\.ed\.jp\/viewer\/info\.html\?id=(\d+)[^|]*) \|$/; + +async function main() { + const events = await collectEvents(); + const src = fs.readFileSync(targetPath, 'utf8'); + const lines = src.split('\n'); + + const inTable = new Map(); + const changes = []; + + lines.forEach((line, index) => { + // Windows の CRLF でも一致するよう、照合時だけ行末の \r を除く + const m = line.replace(/\r$/, '').match(ROW_RE); + if (!m) return; + const [, datesCell, name, , , , id] = m; + inTable.set(id, { index, name, datesCell }); + + const ev = events.get(id); + if (!ev || ev.dates.size === 0) return; + const next = formatDates(ev.dates, ev.dates.size === 1); + if (next !== datesCell) changes.push({ index, name, id, from: datesCell, to: next }); + }); + + console.log(`図書館の予定: カレンダー ${events.size}件 / 表 ${inTable.size}件(${months}か月分を取得)`); + + if (changes.length === 0) { + console.log('日付の変更はありません。'); + } else { + for (const c of changes) { + console.log(`- ${c.name}`); + console.log(` 現在: ${c.from}`); + console.log(` 新規: ${c.to}`); + if (apply) lines[c.index] = lines[c.index].replace(`| ${c.from} |`, `| ${c.to} |`); + } + } + + const missing = [...events.values()].filter((e) => !inTable.has(e.id)); + if (missing.length > 0) { + console.log('\n表にないイベント(内容を確認して追加してください):'); + for (const e of missing) { + console.log(`- ${e.title}(${e.lib}) ${formatDates(e.dates)} ${BASE}info.html?id=${e.id}`); + } + } + + const gone = [...inTable.entries()].filter(([id]) => !events.has(id)); + if (gone.length > 0) { + console.log('\nカレンダーにない行(終了した可能性。必要なら削除してください):'); + for (const [, row] of gone) console.log(`- ${row.name.replace(/^📚 /, '')}(${row.datesCell})`); + } + + if (apply && changes.length > 0) { + fs.writeFileSync(targetPath, lines.join('\n'), 'utf8'); + console.log(`\n${changes.length}件を ${targetPath} に反映しました。`); + } else if (!apply && changes.length > 0) { + console.log('\n(--apply を付けると反映します)'); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); |
