Skip to content

Instantly share code, notes, and snippets.

@brandonjp
Created July 25, 2025 04:00
Show Gist options
  • Save brandonjp/18fc849641c30cb8b0543059db2b547b to your computer and use it in GitHub Desktop.
Save brandonjp/18fc849641c30cb8b0543059db2b547b to your computer and use it in GitHub Desktop.
Schema.org Implementation for CrowdWork Events
/*
* 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