/** * 西永福JAM — https://jam.rinky.info/events * * rinky.info プラットフォーム。meets-otsuka と同じ HTML 構造。 *
*

タイトル

*

アーティスト

*

OPEN 18:30 / START 19:00

* 価格 */ import * as cheerio from "cheerio"; import type { Scraper, VenueMeta } from "./base"; import type { EventInput } from "~/lib/db.server"; export const venue: VenueMeta = { id: "nishieifuku-jam", name: "西永福JAM", url: "https://jam.rinky.info", area: "西永福", }; export const scraper: Scraper = { venue, async scrape(): Promise { const res = await fetch("https://jam.rinky.info/events"); if (!res.ok) throw new Error(`HTTP ${res.status}`); const $ = cheerio.load(await res.text()); const events: EventInput[] = []; $("div.blog-entry.event-wrap").each((_, el) => { const $el = $(el); const date = $el.attr("event-date") ?? ""; if (!date.match(/^\d{4}-\d{2}-\d{2}$/)) return; const $link = $el.find("h2 a").first(); const title = $link.text().trim(); if (!title) return; const detailPath = $link.attr("href") ?? null; const sourceUrl = detailPath ? `${venue.url}${detailPath}` : null; const artist = $el.find("p.act span").map((_, s) => $(s).text().trim()).get().join("、") || null; const timeText = $el.find("p.time").first().text(); const openMatch = timeText.match(/OPEN\s*(\d{2}:\d{2})/i); const startMatch = timeText.match(/START\s*(\d{2}:\d{2})/i); const price = $el.find("span.ticket-price__label").first().text().trim() || null; const bgStyle = $el.find("div.image-bg").attr("style") ?? ""; const imgMatch = bgStyle.match(/url\(["']?([^"')]+)["']?\)/); const imageUrl = imgMatch?.[1] ?? null; const ticketUrl = $el.find("a[href*='livepocket'], a[href*='eplus'], a[href*='pia'], a[href*='ticket'], a[href*='tiget']") .first().attr("href") ?? null; events.push({ venue_id: venue.id, title, artist, date, open_time: openMatch?.[1] ?? null, start_time: startMatch?.[1] ?? null, price, ticket_url: ticketUrl, image_url: imageUrl, source_url: sourceUrl, }); }); return events; }, };