Skip to content

Instantly share code, notes, and snippets.

@JakeLabate
Last active November 15, 2023 13:12
Show Gist options
  • Save JakeLabate/b82b5e6eda165a902d1a7c93b04fb014 to your computer and use it in GitHub Desktop.
Save JakeLabate/b82b5e6eda165a902d1a7c93b04fb014 to your computer and use it in GitHub Desktop.
// hotel framework
function HotelPack(hotel) {
// hotel variables
const hotelType = hotel.type || 'Hotel';
const hotelId = id(hotelType, hotel.name);
const imageLicenseUrl = hotel.copyrightUrl || hotel.homepage;
// hotel amenities array
let amenities = [];
if (hotel.amenitiesArr) {
hotel.amenitiesArr.forEach(function (amenity) {
// hotel amenity image array
let amenityImage = [];
if (amenity.imageUrl) {
amenityImage = {
'@type': 'ImageObject', // https://schema.org/ImageObject
'@id': id('ImageObject', amenity.name),
'url': amenity.imageUrl,
'name': amenity.name,
'contentUrl': amenity.imageUrl,
'caption': amenity.name,
'license': imageLicenseUrl,
'acquireLicensePage': imageLicenseUrl,
'creditText': hotel.name,
'copyrightNotice': 'All right reserved',
'creator': {
'@type': 'Organization', // https://schema.org/Organization
'@id': hotelId,
},
}
}
// if hotel amenity has a name
if (amenity.name) {
amenities.push({
'@type': 'LocationFeatureSpecification', // https://schema.org/LocationFeatureSpecification
'@id': id('LocationFeatureSpecification', amenity.name),
'url': amenity.url || hotel.homepage,
'name': amenity.name,
'value': true,
'image': amenityImage || '',
});
}
});
}
// hotel room group array
let roomGroups = [];
if (hotel.roomGroupsArr) {
hotel.roomGroupsArr.forEach(function (room) {
roomGroups.push({
'@type': 'QuantitativeValue', // https://schema.org/QuantitativeValue
'@id': id('QuantitativeValue', room.numberOfRooms + '-' + room.roomType),
'url': room.url || hotel.homepage,
'unitText': room.roomType,
'value': room.numberOfRooms,
})
});
}
// hotel images array
let images = [];
if (hotel.imageArr) {
hotel.imageArr.forEach(function (image) {
images.push({
'@type': 'ImageObject', // https://schema.org/ImageObject
'@id': id('ImageObject', image.caption + '-' + (hotel.imageArr.indexOf(image) + 1)),
'url': image.url,
'contentUrl': image.url,
'name': image.caption,
'caption': image.caption,
'license': imageLicenseUrl,
'acquireLicensePage': imageLicenseUrl,
'creditText': hotel.name,
'copyrightNotice': 'All right reserved',
'creator': {
'@type': 'Organization', // https://schema.org/Organization
'@id': hotelId,
},
});
});
}
// hotel rooms array
let rooms = [];
if (hotel.roomsArr) {
hotel.roomsArr.forEach(function (room) {
// set room variables
const capturedRoomType = room.type || 'HotelRoom'; // https://schema.org/HotelRoom
const effectiveRoomType = [capturedRoomType, 'Product']; // https://schema.org/HotelRoom + https://schema.org/Product
const roomId = id(capturedRoomType, room.name);
const roomUrl = room.url || hotel.homepage;
const petsBoolean = room.petsAllowedTrueOrFalse || hotel.petsAllowedTrueFalse;
// room amenities array
let roomAmenities = [];
if (room.roomAmenitiesArr) {
room.roomAmenitiesArr.forEach(function (roomAmenity) {
// room amenity variables
const imageLicenseUrl = hotel.copyrightUrl || hotel.homepage;
// room amenity image array
let roomAmenityImage = [];
if (roomAmenity.imageUrl) {
roomAmenityImage = {
'@type': 'ImageObject', // https://schema.org/ImageObject
'@id': id('ImageObject', roomAmenity.name),
'url': roomAmenity.imageUrl,
'name': roomAmenity.name,
'contentUrl': roomAmenity.imageUrl,
'caption': roomAmenity.name,
'license': imageLicenseUrl,
'acquireLicensePage': imageLicenseUrl,
'creditText': hotel.name,
'copyrightNotice': 'All right reserved',
'creator': {
'@type': 'Organization', // https://schema.org/Organization
'@id': hotelId,
},
}
}
// if room amenity has a name
if (roomAmenity.name) {
roomAmenities.push({
'@type': 'LocationFeatureSpecification', // https://schema.org/LocationFeatureSpecification
'@id': id('LocationFeatureSpecification', roomAmenity.name),
'url': roomAmenity.url || roomUrl,
'name': roomAmenity.name,
'value': true,
'image': roomAmenityImage,
});
}
});
}
// room images array
let roomImages = [];
if (room.images) {
room.images.forEach(function (image) {
// if image has a url
if (image.url) {
roomImages.push({
'@type': 'ImageObject', // https://schema.org/ImageObject
'@id': id('ImageObject', image.caption + '-' + (images.indexOf(image) + 1)),
'url': image.url,
'name': image.caption,
'contentUrl': image.url,
'caption': image.caption,
'license': imageLicenseUrl,
'acquireLicensePage': imageLicenseUrl,
'creditText': hotel.name,
'copyrightNotice': 'All right reserved',
'creator': {
'@type': 'Organization', // https://schema.org/Organization
'@id': hotelId,
},
});
}
});
}
// push each room
rooms.push({
'@type': effectiveRoomType, // https://schema.org/HotelRoom + https://schema.org/Product
'@id': roomId,
'url': roomUrl,
'name': room.name,
'smokingAllowed': room.smokingBoolean || false,
'tourBookingPage': room.tourUrl || roomUrl,
'description': room.description,
'aggregateRating': {'@id': id('AggregateRating', hotel.name)},
'brand': {'@id': id('Brand', hotel.name)},
'containedInPlace': {"@id": hotelId},
'amenityFeature': roomAmenities || [],
'petsAllowed': petsBoolean || false,
'photo': roomImages[0] | [],
'image': roomImages || [],
'occupancy': {
'@type': 'QuantitativeValue', // https://schema.org/QuantitativeValue
'@id': id('QuantitativeValue', name + '_occupancy'),
'url': roomUrl,
'name': 'Number of occupants',
'minValue': 1,
'maxValue': room.maxOccupancy,
},
'bed': {
'@type': 'BedDetails', // https://schema.org/BedDetails
'@id': id('BedDetails', name + '_bed'),
'url': roomUrl,
'numberOfBeds': room.bedCount,
'typeOfBed': {
'@type': 'BedType', // https://schema.org/BedType
'@id': id('BedType', name + '_bedType-' + room.bedType),
'url': roomUrl,
'name': room.bedType,
},
},
'offers': {
'@type': 'Offer', // https://schema.org/Offer
'@id': id('Offer', room.name + '_offer'),
'url': roomUrl,
'name': room.name + ' offer',
'businessFunction': 'http://purl.org/goodrelations/v1#LeaseOut',
'availability': 'https://schema.org/InStock', // auditable
'priceValidUntil': new Date().toISOString().split('T')[0], // auditable
'hasMerchantReturnPolicy': {
'@type': 'MerchantReturnPolicy', // https://schema.org/MerchantReturnPolicy
'@id': id('MerchantReturnPolicy', room.name + '_returnPolicy'),
'url': roomUrl,
'name': hotel.name + ' ' + room.name + ' return policy',
'applicableCountry': hotel.location.country || 'USA',
'returnPolicyCountry': hotel.location.country || 'USA',
'returnPolicyCategory': 'MerchantReturnFiniteReturnWindow', // https://schema.org/MerchantReturnEnumeration
},
'priceSpecification': {
'@type': 'UnitPriceSpecification', // https://schema.org/UnitPriceSpecification
'@id': id('UnitPriceSpecification', room.name),
'url': roomUrl,
'name': room.name + ' pricing',
'priceCurrency': 'USD',
'unitCode': 'DAY',
// 'minPrice': room.minPrice,
// 'price': room.minPrice || room.defaultPrice || room.maxPrice, // auditable
// 'maxPrice': room.maxPrice,
}
},
'potentialAction': {
'@type': 'ReserveAction', // https://schema.org/ReserveAction
'@id': id('ReserveAction', room.name),
'url': roomUrl,
'name': 'Reserve',
'scheduledTime': room.checkInTime || hotel.checkInTime,
'startTime': room.checkInTime || hotel.checkInTime,
'provider': {"@id": hotelId},
"target": room.bookUrl,
"object": {
'@type': 'LodgingReservation', // https://schema.org/LodgingReservation
'@id': id('LodgingReservation', room.name),
'url': room.bookUrl || roomUrl,
'name': room.name + ' reservation',
'mainEntityOfPage': room.url,
'checkinTime': room.checkInTime || hotel.checkInTime,
'checkoutTime': room.checkOutTime || hotel.checkOutTime,
'lodgingUnitDescription': room.description,
'lodgingUnitType': room.bedCount + ' total beds',
'provider': {"@id": hotelId},
'reservationFor': {"@id": roomId},
'subjectOf': room.bookUrl,
// 'totalPrice': '',
'potentialAction': {
'@type': 'ReserveAction', // https://schema.org/ReserveAction
'@id': id('ReserveAction', room.name),
'url': room.bookUrl || roomUrl,
'name': 'Reserve',
},
},
}
})
});
}
// page template
const schema = {
'@context': 'https://schema.org',
'@graph': [
// WebSite
{
"@type": "WebSite", // https://schema.org/WebSite
"@id": id('WebSite', hotel.name),
"url": hotel.homepage,
"name": hotel.name,
},
// LodgingBusiness
{
'@type': hotelType || 'Hotel', // https://schema.org/LodgingBusiness or a subtype
'@id': hotelId,
'url': hotel.homepage,
'name': hotel.name,
'description': hotel.description,
'checkinTime': hotel.checkInTime,
'checkoutTime': hotel.checkOutTime,
'petsAllowed': hotel.petsAllowedTrueFalse,
"sameAs": [hotel.facebookUrl, hotel.instagramUrl, hotel.twitterUrl, hotel.linkedinUrl],
'currenciesAccepted': hotel.currenciesAccepted,
'openingHours': hotel.openingHours,
'paymentAccepted': hotel.paymentAccepted,
'availableLanguage': hotel.availableLanguage,
'priceRange': hotel.priceRange,
'logo': {"@id": id('ImageObject', hotel.name + '#logo')},
'image': images || {"@id": id('ImageObject', hotel.name + '#logo')},
'containsPlace': rooms,
'amenityFeature': amenities,
'numberOfRooms': roomGroups || hotel.totalRoomCount,
"brand": {
"@type": "Brand", // https://schema.org/Brand
"@id": id('Brand', hotel.name),
"url": hotel.homepage,
"name": hotel.name,
"slogan": hotel.slogan,
},
'starRating': {
'@type': 'Rating', // https://schema.org/Rating
'@id': id('Rating', hotel.name),
'url': hotel.homepage,
'name': hotel.name + ' Star Rating',
'ratingValue': hotel.rating.value,
},
'address': {
'@type': 'PostalAddress', // https://schema.org/PostalAddress
'@id': id('PostalAddress', hotel.name + hotel.location.street),
'name': hotel.name,
'streetAddress': hotel.location.street,
'addressLocality': hotel.location.city,
'addressRegion': hotel.location.state,
'postalCode': hotel.location.state,
'addressCountry': hotel.location.country,
'telephone': hotel.contact.phone,
'email': hotel.contact.email,
},
},
// ImageObject (logo)
{
"@type": "ImageObject", // https://schema.org/ImageObject
"@id": id('ImageObject', hotel.name + '#logo'),
"url": hotel.logoUrl,
"contentUrl": hotel.logoUrl,
"name": hotel.name + " logo",
"caption": hotel.name + " logo",
"copyrightNotice": "All rights reserved | " + hotel.name,
"creditText": hotel.name,
'license': hotel.copyrightUrl || hotel.homepage,
'acquireLicensePage': hotel.copyrightUrl || hotel.homepage,
"creator": {
'@type': 'Organization', // https://schema.org/Organization
'@id': hotelId,
},
},
],
}
// WebPage + BreadcrumbList
if (hotel.page.name && hotel.page.url) {
schema['@graph'].push([
// WebPage
{
"@type": "WebPage", // https://schema.org/WebPage
"@id": id('WebPage', hotel.name),
"url": hotel.page.url,
"name": hotel.page.name,
"about": {"@id": hotelId},
"isPartOf": {"@id": id('WebSite', hotel.name)},
"breadcrumb": {"@id": id('BreadcrumbList', hotel.name)},
"speakable": {
"@type": "SpeakableSpecification", // https://schema.org/SpeakableSpecification
"@id": id('SpeakableSpecification', hotel.name),
"url": hotel.page.url,
"xPath": [
"/html/head/title",
"/html/head/meta[@name='description']/@content"
]
}
},
// BreadcrumbList
{
"@type": "BreadcrumbList", // https://schema.org/BreadcrumbList
"@id": id('BreadcrumbList', hotel.name),
"url": hotel.page.url,
"name": hotel.page.name,
"itemListElement": [{
"@type": "ListItem", // https://schema.org/ListItem
"@id": id('ListItem', hotel.name + '#breadcrumbListItem'),
"url": hotel.page.url,
"name": hotel.page.name,
"position": 1,
"item": hotel.page.url,
}]
}
])
}
// AggregateRating
if (hotel.page.url && hotel.rating.count && hotel.rating.value) {
schema['@graph'].push({
// AggregateRating (Hotel)
"@type": "AggregateRating", // https://schema.org/AggregateRating
"@id": id('AggregateRating', hotel.name),
"url": hotel.page.url,
"name": hotel.name + " Aggregate Rating",
"ratingCount": hotel.rating.count,
"ratingValue": hotel.rating.value,
"bestRating": 5, // seems safe enough to use static value of 5
"itemReviewed": {
"@type": "Product", // https://schema.org/Product
"@id": id('Product', hotel.name + '-' + hotel.page.url),
"url": hotel.page.url,
"name": hotel.name,
"aggregateRating": {
"@type": "AggregateRating", // https://schema.org/AggregateRating
"@id": id('AggregateRating', hotel.name),
"url": hotel.page.url,
"name": hotel.name + " Aggregate Rating",
"ratingCount": hotel.rating.count,
"ratingValue": hotel.rating.value,
"bestRating": 5 // seems safe enough to use static value of 5
},
"offers": {
"@type": "AggregateOffer", // https://schema.org/AggregateOffer
"@id": id('AggregateOffer', hotel.name),
"url": hotel.page.url,
"priceCurrency": "USD"
// "lowPrice": hotel.lowestPrice,
// "highPrice": hotel.highestPrice,
}
}
})
}
// VideoObject
if (hotel.page.video) {
if (hotel.page.video.thumbnailUrl && hotel.page.video.uploadDateISO && (hotel.page.video.contentUrl || hotel.page.video.embedUrl)) {
schema['@graph'].push({
"@type": "VideoObject", // https://schema.org/VideoObject
"@id": id('VideoObject', hotel.name),
"url": hotel.page.url,
"name": hotel.page.video.name,
"description": hotel.page.video.description,
"thumbnailUrl": hotel.page.video.thumbnailUrl,
"uploadDate": hotel.page.video.uploadDateISO,
"duration": "PT" + hotel.page.video.duration.min + "M" + hotel.page.video.duration.sec + "S",
"contentUrl": hotel.page.video.contentUrl,
"embedUrl": hotel.page.video.embedUrl,
"regionsAllowed": "US,NL"
})
}
}
// return schema
return schema;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment