summaryrefslogtreecommitdiff
path: root/scripts/seed-mosquitone.ts
blob: e3d532d6e5e19f1401861aa85868a209de52d0ce (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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); });