Created
July 25, 2025 04:00
-
-
Save brandonjp/18fc849641c30cb8b0543059db2b547b to your computer and use it in GitHub Desktop.
Schema.org Implementation for CrowdWork Events
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Recommended Schema.org Implementation for CrowdWork Events | |
* Version: 1.0.0 | |
* | |
* This script intercepts the API call made by embed.js to generate | |
* schema.org markup immediately when event data becomes available. | |
* Place this script BEFORE the embed.js script tag. | |
*/ | |
// Configuration - UPDATE THESE VALUES FOR YOUR THEATRE | |
const SchemaConfig = { | |
theatreName: "Your Theatre Name", | |
theatreAddress: { | |
streetAddress: "123 Theatre Street", | |
locality: "Your City", | |
region: "Your State", | |
country: "US", | |
postalCode: "12345" | |
}, | |
organizationInfo: { | |
"@type": "Organization", | |
"name": "Your Theatre Organization", | |
"url": "https://yourwebsite.com" | |
}, | |
defaultCurrency: "USD" | |
}; | |
// Main implementation: Intercept fetch API to catch event data | |
function interceptCrowdWorkAPI() { | |
// Store reference to original fetch | |
const originalFetch = window.fetch; | |
window.fetch = async function(...args) { | |
const response = await originalFetch.apply(this, arguments); | |
// Check if this is the CrowdWork API call for events | |
const url = args[0]; | |
if (typeof url === 'string' && | |
url.includes('/api/v2/') && | |
url.includes('?cache=') && | |
(url.includes('/shows') || url.includes('/classes'))) { | |
// Clone response to avoid consuming the original stream | |
const clonedResponse = response.clone(); | |
try { | |
const apiData = await clonedResponse.json(); | |
if (apiData && apiData.data) { | |
// Process the raw API data into events format | |
const events = processAPIDataToEvents(apiData); | |
generateSchemaImmediately(events); | |
} | |
} catch (error) { | |
console.warn('Schema generation error:', error); | |
} | |
} | |
return response; | |
}; | |
} | |
// Process raw API data into structured events | |
function processAPIDataToEvents(apiData) { | |
const events = []; | |
Object.keys(apiData.data).forEach((key) => { | |
const show = apiData.data[key]; | |
const showDates = show.dates || []; | |
// Skip shows that are past their display date (for cards view) | |
const today = new Date(); | |
today.setHours(0, 0, 0, 0); | |
if (show.display_until && new Date(show.display_until) < today) { | |
return; | |
} | |
showDates.forEach((date) => { | |
// Handle image URL (dev vs prod) | |
let imgURL = show.img?.url || ''; | |
if (imgURL && !imgURL.startsWith('http')) { | |
imgURL = `https://crowdwork.com${imgURL}`; | |
} | |
events.push({ | |
title: show.name, | |
start: date, | |
description: show.description?.body || "No description given.", | |
img: imgURL, | |
cost: show.cost?.formatted || '', | |
show_url: show.url || '', | |
timezone: show.timezone || 'America/Chicago', | |
grouped_dates: show.grouped_dates || {}, | |
next_date: show.next_date || date | |
}); | |
}); | |
}); | |
return events; | |
} | |
// Generate schema.org markup immediately when data is available | |
function generateSchemaImmediately(events) { | |
if (!events || !Array.isArray(events) || events.length === 0) { | |
return; | |
} | |
// Generate schema for each event | |
const schemas = events.map(event => generateEventSchema(event)); | |
// Insert into page head immediately | |
insertSchemaIntoHead(schemas); | |
console.log('✅ Generated schema.org markup for', events.length, 'events'); | |
} | |
// Generate schema.org Event markup for a single event | |
function generateEventSchema(event) { | |
// Determine event type based on title content | |
const eventType = determineEventType(event.title); | |
// Parse start date | |
const startDate = new Date(event.start).toISOString(); | |
// Base schema object | |
const schema = { | |
"@context": "https://schema.org", | |
"@type": eventType, | |
"name": event.title, | |
"startDate": startDate, | |
"eventStatus": "https://schema.org/EventScheduled" | |
}; | |
// Add description if meaningful | |
if (event.description && event.description !== "No description given.") { | |
schema.description = cleanDescription(event.description); | |
} | |
// Add image if available | |
if (event.img) { | |
schema.image = event.img; | |
} | |
// Add event URL | |
if (event.show_url) { | |
schema.url = event.show_url; | |
} | |
// Add location information | |
schema.location = { | |
"@type": "PerformingArtsTheater", | |
"name": SchemaConfig.theatreName, | |
"address": { | |
"@type": "PostalAddress", | |
"streetAddress": SchemaConfig.theatreAddress.streetAddress, | |
"addressLocality": SchemaConfig.theatreAddress.locality, | |
"addressRegion": SchemaConfig.theatreAddress.region, | |
"postalCode": SchemaConfig.theatreAddress.postalCode, | |
"addressCountry": SchemaConfig.theatreAddress.country | |
} | |
}; | |
// Add organizer | |
schema.organizer = SchemaConfig.organizationInfo; | |
// Add pricing information if available | |
if (event.cost) { | |
const price = extractPriceFromCost(event.cost); | |
if (price && price !== "0") { | |
schema.offers = { | |
"@type": "Offer", | |
"price": price, | |
"priceCurrency": SchemaConfig.defaultCurrency, | |
"availability": "https://schema.org/InStock", | |
"url": event.show_url || schema.url | |
}; | |
} | |
} | |
return schema; | |
} | |
// Determine appropriate schema.org event type | |
function determineEventType(title) { | |
const titleLower = title.toLowerCase(); | |
if (titleLower.includes('class') || titleLower.includes('workshop') || titleLower.includes('lesson')) { | |
return "EducationEvent"; | |
} | |
if (titleLower.includes('concert') || titleLower.includes('music') || titleLower.includes('band')) { | |
return "MusicEvent"; | |
} | |
if (titleLower.includes('comedy') || titleLower.includes('stand-up') || titleLower.includes('improv')) { | |
return "ComedyEvent"; | |
} | |
if (titleLower.includes('dance')) { | |
return "DanceEvent"; | |
} | |
if (titleLower.includes('film') || titleLower.includes('movie') || titleLower.includes('screening')) { | |
return "ScreeningEvent"; | |
} | |
// Default to TheaterEvent for most performances | |
return "TheaterEvent"; | |
} | |
// Clean HTML from description text | |
function cleanDescription(description) { | |
// Remove HTML tags and decode entities | |
const tempDiv = document.createElement('div'); | |
tempDiv.innerHTML = description; | |
return tempDiv.textContent || tempDiv.innerText || ''; | |
} | |
// Extract numeric price from formatted cost string | |
function extractPriceFromCost(costString) { | |
const match = costString.match(/\$?(\d+(?:\.\d{2})?)/); | |
return match ? match[1] : null; | |
} | |
// Insert schema markup into page head | |
function insertSchemaIntoHead(schemas) { | |
// Remove any existing schemas we've added | |
const existingSchemas = document.querySelectorAll('script[data-schema-source="crowdwork-events"]'); | |
existingSchemas.forEach(script => script.remove()); | |
// Add each schema as a separate script tag | |
schemas.forEach((schema, index) => { | |
const script = document.createElement('script'); | |
script.type = 'application/ld+json'; | |
script.setAttribute('data-schema-source', 'crowdwork-events'); | |
script.setAttribute('data-event-index', index.toString()); | |
script.textContent = JSON.stringify(schema, null, 2); | |
// Insert into head | |
document.head.appendChild(script); | |
}); | |
// Dispatch custom event for any listeners | |
window.dispatchEvent(new CustomEvent('crowdworkSchemaGenerated', { | |
detail: { | |
schemas: schemas, | |
count: schemas.length | |
} | |
})); | |
} | |
// Initialize the schema generation | |
function initializeCrowdWorkSchema() { | |
console.log('Initializing CrowdWork schema.org generation...'); | |
interceptCrowdWorkAPI(); | |
console.log('✅ CrowdWork schema generation ready'); | |
} | |
// Auto-initialize when script loads | |
if (document.readyState === 'loading') { | |
document.addEventListener('DOMContentLoaded', initializeCrowdWorkSchema); | |
} else { | |
initializeCrowdWorkSchema(); | |
} | |
// Export for manual control if needed | |
window.CrowdWorkSchema = { | |
init: initializeCrowdWorkSchema, | |
config: SchemaConfig, | |
generateFromEvents: generateSchemaImmediately | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment