feat: add live ride status via Queue-Times.com API
All checks were successful
Build and Deploy / Build & Push (push) Successful in 2m51s
All checks were successful
Build and Deploy / Build & Push (push) Successful in 2m51s
Park detail pages now show real-time ride open/closed status and wait times sourced from Queue-Times.com (updates every 5 min) when a park is operating. Falls back to the Six Flags schedule API for off-hours or parks without a Queue-Times mapping. - lib/queue-times-map.ts: maps all 24 park IDs to Queue-Times park IDs - lib/scrapers/queuetimes.ts: fetches and parses queue_times.json with 5-minute ISR cache; returns LiveRidesResult with isOpen + waitMinutes - app/park/[id]/page.tsx: tries Queue-Times first; renders LiveRideList with Live badge and per-ride wait times; falls back to RideList for schedule data when live data is unavailable - README: documents two-tier ride status approach Attribution: Queue-Times.com (displayed in UI per their API terms) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
115
lib/scrapers/queuetimes.ts
Normal file
115
lib/scrapers/queuetimes.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Queue-Times.com live ride status scraper.
|
||||
*
|
||||
* API: https://queue-times.com/parks/{id}/queue_times.json
|
||||
* Updates every 5 minutes while the park is operating.
|
||||
* Attribution required per their terms: "Powered by Queue-Times.com"
|
||||
* See: https://queue-times.com/en-US/pages/api
|
||||
*/
|
||||
|
||||
const BASE = "https://queue-times.com/parks";
|
||||
|
||||
const HEADERS = {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
|
||||
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
Accept: "application/json",
|
||||
};
|
||||
|
||||
export interface LiveRide {
|
||||
name: string;
|
||||
isOpen: boolean;
|
||||
waitMinutes: number;
|
||||
lastUpdated: string; // ISO 8601
|
||||
}
|
||||
|
||||
export interface LiveRidesResult {
|
||||
rides: LiveRide[];
|
||||
/** ISO timestamp of when we fetched the data */
|
||||
fetchedAt: string;
|
||||
}
|
||||
|
||||
interface QTRide {
|
||||
id: number;
|
||||
name: string;
|
||||
is_open: boolean;
|
||||
wait_time: number;
|
||||
last_updated: string;
|
||||
}
|
||||
|
||||
interface QTLand {
|
||||
id: number;
|
||||
name: string;
|
||||
rides: QTRide[];
|
||||
}
|
||||
|
||||
interface QTResponse {
|
||||
lands: QTLand[];
|
||||
rides: QTRide[]; // top-level rides (usually empty, rides live in lands)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch live ride open/closed status and wait times for a park.
|
||||
*
|
||||
* Returns null when:
|
||||
* - The park has no Queue-Times mapping
|
||||
* - The request fails
|
||||
* - The response contains no rides
|
||||
*
|
||||
* Pass revalidate (seconds) to control Next.js ISR cache lifetime.
|
||||
* Defaults to 300s (5 min) to match Queue-Times update frequency.
|
||||
*/
|
||||
export async function fetchLiveRides(
|
||||
queueTimesId: number,
|
||||
revalidate = 300,
|
||||
): Promise<LiveRidesResult | null> {
|
||||
const url = `${BASE}/${queueTimesId}/queue_times.json`;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
headers: HEADERS,
|
||||
next: { revalidate },
|
||||
} as RequestInit & { next: { revalidate: number } });
|
||||
|
||||
if (!res.ok) return null;
|
||||
|
||||
const json = (await res.json()) as QTResponse;
|
||||
|
||||
const rides: LiveRide[] = [];
|
||||
|
||||
// Rides are nested inside lands
|
||||
for (const land of json.lands ?? []) {
|
||||
for (const r of land.rides ?? []) {
|
||||
if (!r.name) continue;
|
||||
rides.push({
|
||||
name: r.name,
|
||||
isOpen: r.is_open,
|
||||
waitMinutes: r.wait_time ?? 0,
|
||||
lastUpdated: r.last_updated,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Also capture any top-level rides (rare but possible)
|
||||
for (const r of json.rides ?? []) {
|
||||
if (!r.name) continue;
|
||||
rides.push({
|
||||
name: r.name,
|
||||
isOpen: r.is_open,
|
||||
waitMinutes: r.wait_time ?? 0,
|
||||
lastUpdated: r.last_updated,
|
||||
});
|
||||
}
|
||||
|
||||
if (rides.length === 0) return null;
|
||||
|
||||
// Open rides first, then alphabetical within each group
|
||||
rides.sort((a, b) => {
|
||||
if (a.isOpen !== b.isOpen) return a.isOpen ? -1 : 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
return { rides, fetchedAt: new Date().toISOString() };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user