/** * 吉祥寺 WARP — http://warp.rinky.info/schedules * * WordPress カスタムテーマ。年月は: *

2026
05

* * イベント構造: *
*
DD...
*

タイトル

*
*

OPEN / START
HH:MM / HH:MM

*

ADV / DOOR
¥XXXX / ¥XXXX

*
*
*
*/ import * as cheerio from "cheerio"; import type { Scraper, VenueMeta } from "./base"; import type { EventInput } from "~/lib/db.server"; export const venue: VenueMeta = { id: "warp-kichijoji", name: "吉祥寺 WARP", url: "http://warp.rinky.info", area: "吉祥寺", }; export const scraper: Scraper = { venue, async scrape(): Promise { const res = await fetch("http://warp.rinky.info/schedules"); if (!res.ok) throw new Error(`HTTP ${res.status}`); const $ = cheerio.load(await res.text()); const events: EventInput[] = []; // Extract year + month from

2026
05

const h3Text = $("h3").first().text().trim(); // e.g. "2026\n05" const yearMonthMatch = h3Text.match(/(\d{4})\D*(\d{2})/); if (!yearMonthMatch) return events; const year = yearMonthMatch[1]; const month = yearMonthMatch[2]; $("article.schedules-box").each((_, el) => { const $el = $(el); // Day from article id: "box-03-23546" → "03" const id = $el.attr("id") ?? ""; const dayMatch = id.match(/^box-(\d{2})-/); if (!dayMatch) return; const day = dayMatch[1]; const date = `${year}-${month}-${day}`; const title = $el.find("h4").first().text().replace(//gi, " ").trim(); if (!title) return; // First notes-wrapper

contains OPEN/START times const $notes = $el.find("section.notes-wrapper p"); const timeStrong = $notes.eq(0).find("span.strong").text().trim(); // e.g. "18:30 / 19:00" const [openTime, startTime] = timeStrong.split("/").map((s) => s.trim()); // Second

contains ADV/DOOR price const priceStrong = $notes.eq(1).find("span.strong").text().trim(); // e.g. "¥3,000 / ¥3,500" const price = priceStrong !== "TBA / TBA" && priceStrong ? priceStrong : null; // Image: prefer data-src (lazy), fall back to noscript img src const $flyer = $el.find("section.flyer img").first(); const rawImg = $flyer.attr("data-src") ?? $el.find("section.flyer noscript img").first().attr("src") ?? null; // Strip ShortPixel CDN prefix if present const imageUrl = rawImg ? rawImg.replace(/^https?:\/\/sp-ao\.shortpixel\.ai\/client\/[^/]+\//, "") : null; events.push({ venue_id: venue.id, title, artist: null, date, open_time: isTime(openTime) ? openTime : null, start_time: isTime(startTime) ? startTime : null, price, ticket_url: $el.find("a[href*='livepocket'], a[href*='eplus'], a[href*='pia']").first().attr("href") ?? null, image_url: imageUrl, source_url: null, }); }); return events; }, }; function isTime(s: string | undefined): boolean { return !!s && /^\d{2}:\d{2}$/.test(s.trim()); }