🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
108 lines
3.2 KiB
Markdown
108 lines
3.2 KiB
Markdown
{\rtf1\ansi\ansicpg1252\cocoartf2822
|
|
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;}
|
|
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
|
|
{\*\expandedcolortbl;;\cspthree\c0\c0\c0;}
|
|
\margl1440\margr1440\vieww11520\viewh8400\viewkind0
|
|
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
|
|
|
|
\f0\fs28 \cf2 Got it \'97 if the main site dev is a bottleneck, scraping is your best move.\
|
|
\
|
|
Here\'92s a battle-tested, lightweight scraping plan to monitor https://blackcanyontickets.com/events and detect when the currently active event changes, then extract details from the redirected event page to trigger a calendar update.\
|
|
\
|
|
\uc0\u11835 \
|
|
\
|
|
\uc0\u55358 \u56816 Scraper Stack Recommendation\
|
|
\
|
|
Tool Purpose\
|
|
node-fetch or axios Follow (or block) redirect from /events\
|
|
cheerio Parse HTML from the actual event page\
|
|
node-cron or supabase.functions.schedule() Run on a schedule\
|
|
fs or Supabase Store last seen event slug for diffing\
|
|
\
|
|
\
|
|
\uc0\u11835 \
|
|
\
|
|
\uc0\u9989 Working Scraper Skeleton (Node.js)\
|
|
\
|
|
import fetch from 'node-fetch';\
|
|
import cheerio from 'cheerio';\
|
|
import fs from 'fs/promises';\
|
|
\
|
|
const REDIRECT_URL = 'https://blackcanyontickets.com/events';\
|
|
const BASE_URL = 'https://blackcanyontickets.com';\
|
|
\
|
|
async function getCurrentEventSlug() \{\
|
|
const res = await fetch(REDIRECT_URL, \{ redirect: 'manual' \});\
|
|
return res.headers.get('location') || null;\
|
|
\}\
|
|
\
|
|
async function fetchEventDetails(slug) \{\
|
|
const res = await fetch(`$\{BASE_URL\}$\{slug\}`);\
|
|
const html = await res.text();\
|
|
const $ = cheerio.load(html);\
|
|
\
|
|
return \{\
|
|
slug,\
|
|
title: $('h1').first().text().trim(),\
|
|
date: $('[data-event-date]').text().trim(), // tweak selector to match\
|
|
time: $('[data-event-time]').text().trim(), // tweak selector to match\
|
|
\};\
|
|
\}\
|
|
\
|
|
async function loadLastSeenSlug() \{\
|
|
try \{\
|
|
return await fs.readFile('./last_slug.txt', 'utf-8');\
|
|
\} catch \{\
|
|
return null;\
|
|
\}\
|
|
\}\
|
|
\
|
|
async function saveLastSeenSlug(slug) \{\
|
|
await fs.writeFile('./last_slug.txt', slug);\
|
|
\}\
|
|
\
|
|
async function run() \{\
|
|
const currentSlug = await getCurrentEventSlug();\
|
|
if (!currentSlug) return console.log('No event redirect found');\
|
|
\
|
|
const lastSeen = await loadLastSeenSlug();\
|
|
if (currentSlug === lastSeen) \{\
|
|
return console.log('No new event');\
|
|
\}\
|
|
\
|
|
const details = await fetchEventDetails(currentSlug);\
|
|
console.log('\uc0\u55356 \u57247 \u65039 New event found:', details);\
|
|
\
|
|
// TODO: Push to calendar / Supabase / webhook\
|
|
\
|
|
await saveLastSeenSlug(currentSlug);\
|
|
\}\
|
|
\
|
|
run();\
|
|
\
|
|
\
|
|
\uc0\u11835 \
|
|
\
|
|
\uc0\u55357 \u56658 Optional: Add Cron Job\
|
|
\
|
|
With node-cron:\
|
|
\
|
|
import cron from 'node-cron';\
|
|
\
|
|
cron.schedule('*/15 * * * *', () => \{\
|
|
run();\
|
|
\});\
|
|
\
|
|
Or deploy to:\
|
|
\'95 A lightweight VM\
|
|
\'95 Supabase Edge Function (on trigger)\
|
|
\'95 GitHub Actions (with secrets)\
|
|
\
|
|
\uc0\u11835 \
|
|
\
|
|
\uc0\u55357 \u56615 Next Steps\
|
|
\'95 Paste in a real event HTML snippet if you want me to write exact cheerio selectors\
|
|
\'95 Want to output .ics or send it straight to Google Calendar?\
|
|
\'95 Want this wrapped as a Docker container or systemd service?\
|
|
\
|
|
You\'92re one command away from auto-watching your own platform.} |