summaryrefslogtreecommitdiff
path: root/scripts/seed-mosquitone.ts
diff options
context:
space:
mode:
authoryyamashita <yyamashita@mosquit.one>2026-05-11 00:06:52 +0900
committeryyamashita <yyamashita@mosquit.one>2026-05-11 00:06:52 +0900
commite9e576abd9d6c6030aa4bb290e869890831488ad (patch)
treeec521f62ddffda13c30f5c964e01b9daa1b52851 /scripts/seed-mosquitone.ts
parent609dc6a3769d85e1cc4a8f06af58165be86b598c (diff)
Add lists feature (band recommendation lists with history)
New lists, list_entries, list_revisions tables; full CRUD routes under /lists; nav link in root. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'scripts/seed-mosquitone.ts')
-rw-r--r--scripts/seed-mosquitone.ts137
1 files changed, 137 insertions, 0 deletions
diff --git a/scripts/seed-mosquitone.ts b/scripts/seed-mosquitone.ts
new file mode 100644
index 0000000..e3d532d
--- /dev/null
+++ b/scripts/seed-mosquitone.ts
@@ -0,0 +1,137 @@
+#!/usr/bin/env npx tsx
+/**
+ * Seeds a "mosquitone と共演したバンド" list by fetching Gatsby page-data JSON.
+ * Usage: npx tsx scripts/seed-mosquitone.ts
+ * Override DB path: DB_PATH=/path/to/whois.db npx tsx scripts/seed-mosquitone.ts
+ */
+
+import { createBandList, getBandListBySlug, toSlug } from "../app/lib/db.server";
+
+interface LiveEvent {
+ date: string;
+ venueName: string;
+ description: string;
+ title: string;
+ published?: boolean;
+ [key: string]: unknown;
+}
+
+interface PageData {
+ result?: {
+ data?: {
+ amplify?: {
+ listEvents?: {
+ items: LiveEvent[];
+ };
+ };
+ };
+ };
+}
+
+const PAGE_DATA_URL = "https://www.mosquit.one/page-data/live/page-data.json";
+
+async function main() {
+ console.log("Fetching mosquit.one live data...");
+ const res = await fetch(PAGE_DATA_URL);
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
+ const json = await res.json() as PageData;
+
+ const items = json?.result?.data?.amplify?.listEvents?.items ?? [];
+ console.log(`Found ${items.length} events`);
+
+ // band_name → [{ date, venue }]
+ const coPerformers = new Map<string, { date: string; venue: string }[]>();
+
+ for (const event of items) {
+ const rawDesc = event.description ?? "";
+ const date = event.date ?? "";
+ const venue = (event.venueName ?? "").trim();
+
+ // Split on actual newlines or literal \n, then find the performer line
+ // (some events have OPEN/START on the first line and performers on the second)
+ const lines = rawDesc.split(/\n|\\n/);
+ const perfLine = lines.find((l) =>
+ !l.includes("://") && ( // skip URL-containing lines
+ l.includes("//") || (l.split("/").length > 2 && !/^\d/.test(l.trim()))
+ )
+ );
+ if (!perfLine) continue;
+
+ // Remove "出演:" / "出演者" prefix
+ const cleaned = perfLine.replace(/^(出演者?[::]?\s*)/u, "").trim();
+
+ // Must still contain // or " / " after cleanup
+ if (!cleaned.includes("//") && !cleaned.includes(" / ")) continue;
+
+ // Split by " // " or " / "
+ const parts = cleaned.split(/\s*\/\/\s*|\s*\/\s*/);
+
+ for (const raw of parts) {
+ // Remove parenthetical suffixes like "(東京)" "(いわき)"
+ let name = raw.replace(/[\((][^))]*[\))]/gu, "").trim();
+ // Remove any remaining literal \n and opening-paren-without-close
+ name = name.replace(/\\n/g, "").replace(/[\((].*$/, "").trim();
+ // Remove stray closing parentheses
+ name = name.replace(/[\))]/g, "").trim();
+
+ // Validation filters
+ if (!name || name.length < 2) continue;
+ if (/mosquitone/iu.test(name)) continue;
+ if (/^\d/.test(name)) continue; // starts with number (dates etc.)
+ if (/\d{1,2}:\d{2}/.test(name)) continue; // contains time HH:MM
+ if (/^(OPEN|START)/i.test(name)) continue;
+ if (/出演|二日間|開催|開場/.test(name)) continue;
+ if (/\.(com|net|jp|org|co)\b/.test(name)) continue; // domain names
+ if (name.length > 60) continue;
+
+ const appearances = coPerformers.get(name) ?? [];
+ appearances.push({ date, venue });
+ coPerformers.set(name, appearances);
+ }
+ }
+
+ console.log(`Parsed ${coPerformers.size} co-performers`);
+
+ // Build entries sorted by band name
+ const entries = [...coPerformers.entries()]
+ .sort(([a], [b]) => a.localeCompare(b, "ja"))
+ .map(([band_name, appearances]) => {
+ appearances.sort((a, b) => a.date.localeCompare(b.date));
+ const note = appearances
+ .map(({ date, venue }) => {
+ const displayDate = date.replace(/-/g, "/");
+ return venue ? `${displayDate} に ${venue} で共演` : `${displayDate} に共演`;
+ })
+ .join("、");
+ return { band_name, note };
+ });
+
+ const title = "mosquitone と共演したバンド";
+ const slug = toSlug(title);
+
+ // Check for existing list
+ const existing = getBandListBySlug(slug);
+ if (existing) {
+ console.log(`List already exists (slug: "${slug}"), skipping.`);
+ console.log(` url: /lists/of/${existing.id}`);
+ return;
+ }
+
+ const id = crypto.randomUUID();
+ const list = createBandList({
+ id,
+ slug,
+ title,
+ description: "mosquitone の共演バンドを自動収集したリストです。",
+ entries,
+ message: "seed-mosquitone スクリプトによる自動生成",
+ ip_address: "cli",
+ });
+
+ console.log(`Created list: ${list.title}`);
+ console.log(` id: ${list.id}`);
+ console.log(` entries: ${entries.length}件`);
+ console.log(` url: /lists/of/${list.id}`);
+}
+
+main().catch((e) => { console.error(e); process.exit(1); });