From e459bb23d1e705b3191d387d9a6201f748681b6f Mon Sep 17 00:00:00 2001 From: Lachlan Mason Date: Sat, 8 Nov 2025 23:32:01 +1100 Subject: [PATCH 1/4] re added and changed te2.js w/ Gold Coast Movie World and Sea World destinations --- lib/parks/te2/te2.js | 739 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 739 insertions(+) create mode 100644 lib/parks/te2/te2.js diff --git a/lib/parks/te2/te2.js b/lib/parks/te2/te2.js new file mode 100644 index 00000000..fbe4130d --- /dev/null +++ b/lib/parks/te2/te2.js @@ -0,0 +1,739 @@ +import {URL} from 'url'; +import moment from 'moment-timezone'; + +import Destination from '../destination.js'; +import {attractionType, statusType, queueType, scheduleType, entityType} from '../parkTypes.js'; + +// Ride indicator labels used to identify attractions with wait times +const RIDE_INDICATOR_LABELS = new Set(['thrill level', 'rider height', 'ages']); + +// Schedule types +const SCHEDULE_TYPE_PERFORMANCE = 'Performance Time'; + +// Schedule label identifiers +const PARK_SCHEDULE_LABELS = ['park', 'gate']; + +/** + * Base destination for TE2-powered parks. + * Handles basic auth using the configured username/password. + */ +export class TE2Destination extends Destination { + /** + * Create a new TE2Destination object + * @param {object} options Configuration options + * @param {string} [options.timezone] Park timezone (default: Australia/Brisbane) + * @param {string} [options.destinationId] Unique destination identifier + * @param {string} [options.venueId] TE2 API venue ID + * @param {string} [options.subdomain] TE2 API subdomain + * @param {string} [options.apidomain] TE2 API domain + * @param {string} [options.apiuser] TE2 API username + * @param {string} [options.apipass] TE2 API password + * @param {Array} [options.rideTypes] Categories to classify as rides + * @param {Array} [options.diningTypes] Categories to classify as dining + * @param {Array} [options.showTypes] Categories to classify as shows + * @param {number} [options.eventScheduleDays] Days to fetch for event schedule (default: 14) + */ + constructor(options = {}) { + options.timezone = options.timezone || 'Australia/Brisbane'; + options.destinationId = options.destinationId || ''; + options.venueId = options.venueId || ''; + options.subdomain = options.subdomain || 'vrtp'; + options.apidomain = options.apidomain || 'te2.biz'; + options.apiuser = options.apiuser || ''; + options.apipass = options.apipass || ''; + options.rideTypes = options.rideTypes || ['Ride', 'Coasters', 'Family', 'ThrillRides', 'Kids', 'Rides & Attractions']; + options.diningTypes = options.diningTypes || ['Snacks', 'wpDining', 'Meals', 'Dining']; + options.showTypes = options.showTypes || ['Shows', 'Show', 'Entertainment', 'Live Entertainment', 'Presentation']; + options.eventScheduleDays = options.eventScheduleDays || 14; + + // allow configuring credentials via TE2_* env vars + options.configPrefixes = ['TE2'].concat(options.configPrefixes || []); + + super(options); + + if (!this.config.destinationId) throw new Error('Missing destinationId'); + if (!this.config.venueId) throw new Error('Missing venueId'); + if (!this.config.subdomain) throw new Error('Missing subdomain'); + if (!this.config.apidomain) throw new Error('Missing apidomain'); + if (!this.config.apiuser) throw new Error('Missing apiuser'); + if (!this.config.apipass) throw new Error('Missing apipass'); + + this.config.apiBase = this.config.apiBase || `https://${this.config.subdomain}.${this.config.apidomain}`; + + const baseURLHostname = new URL(this.config.apiBase).hostname; + + this.http.injectForDomain({ + hostname: baseURLHostname, + }, async (method, url, data, options = {}) => { + const requestUrl = new URL(url); + + options.headers = { + ...(options.headers || {}), + 'Content-Type': 'application/json', + }; + + if (requestUrl.pathname.startsWith('/rest/')) { + const credentials = Buffer.from(`${this.config.apiuser}:${this.config.apipass}`).toString('base64'); + options.headers.Authorization = `Basic ${credentials}`; + } + }); + } + + /** + * Fetch current POI status data including wait times and operational status + * @return {Promise} POI status data + */ + async getPOIStatus() { + '@cache|1'; + const resp = await this.http('GET', `${this.config.apiBase}/rest/venue/${this.config.venueId}/poi/all/status`); + return resp?.body ?? resp; + } + + /** + * Fetch venue/destination metadata + * @return {Promise} Destination data including name and location + */ + async getDestinationData() { + '@cache|1440'; + const resp = await this.http('GET', `${this.config.apiBase}/rest/venue/${this.config.venueId}`); + return resp?.body ?? resp; + } + + /** + * Fetch all Points of Interest (attractions, dining, shows, etc.) for this venue + * @return {Promise} Array of POI objects + */ + async getPOIData() { + '@cache|1440'; + const resp = await this.http('GET', `${this.config.apiBase}/rest/venue/${this.config.venueId}/poi/all`); + return resp?.body ?? resp; + } + + /** + * Fetch category definitions from the TE2 API + * @return {Promise} Category data with POI associations + * @private + */ + async _fetchCategories() { + '@cache|1440'; + const resp = await this.http('GET', `${this.config.apiBase}/rest/app/${this.config.venueId}/displayCategories`); + return resp?.body ?? resp; + } + + /** + * Parse category data to find POI entities matching the given types + * Recursively includes child categories based on parent relationships + * @param {object} params Parameters + * @param {Array} params.initialTypes Initial category types to search for + * @return {Promise} Object containing matched types and entity IDs + * @private + */ + async _getParsedCategories({initialTypes}) { + const types = Array.isArray(initialTypes) ? [...initialTypes] : []; + const entities = []; + + const categoryData = await this._fetchCategories(); + if (!Array.isArray(categoryData?.categories)) { + return { + types, + entities, + }; + } + + categoryData.categories.forEach((cat) => { + if (types.indexOf(cat.label) >= 0) { + if (types.indexOf(cat.id) < 0) { + types.push(cat.id); + } + if (Array.isArray(cat.poi)) { + entities.push(...cat.poi); + } + } + if (cat.parent && types.indexOf(cat.parent) >= 0) { + if (types.indexOf(cat.id) < 0) { + types.push(cat.id); + } + if (Array.isArray(cat.poi)) { + entities.push(...cat.poi); + } + } + }); + + return { + types, + entities: [...new Set(entities)], + }; + } + + /** + * Get parsed category data for attraction/ride types + * @return {Promise} Object with types array and entities array + */ + async getAttractionTypes() { + return await this._getParsedCategories({ + initialTypes: this.config.rideTypes, + }); + } + + /** + * Get parsed category data for dining types + * @return {Promise} Object with types array and entities array + */ + async getDiningTypes() { + return await this._getParsedCategories({ + initialTypes: this.config.diningTypes, + }); + } + + /** + * Get parsed category data for show/entertainment types + * @return {Promise} Object with types array and entities array + */ + async getShowTypes() { + return await this._getParsedCategories({ + initialTypes: this.config.showTypes, + }); + } + + /** + * Build a base entity object from TE2 API data + * Extracts common fields like name, ID, and location + * @param {object} data Raw entity data from TE2 API + * @return {object} Base entity object with standardized fields + */ + buildBaseEntityObject(data) { + const entity = super.buildBaseEntityObject(data); + + if (data) { + entity.name = data.name || data.label; + entity._id = data.id || undefined; + + if (data.location) { + if (data.location.lon !== undefined && data.location.lat !== undefined) { + const lon = Number(data.location.lon); + const lat = Number(data.location.lat); + if (!Number.isNaN(lon) && !Number.isNaN(lat)) { + entity.location = { + longitude: lon, + latitude: lat, + }; + } + } + if (data.location.center?.lon !== undefined && data.location.center?.lat !== undefined) { + const lon = Number(data.location.center.lon); + const lat = Number(data.location.center.lat); + if (!Number.isNaN(lon) && !Number.isNaN(lat)) { + entity.location = { + longitude: lon, + latitude: lat, + }; + } + } + } + } + + return entity; + } + + /** + * Build the destination entity representing this venue + * @return {Promise} Destination entity object + */ + async buildDestinationEntity() { + const destinationData = await this.getDestinationData(); + const name = destinationData?.name || destinationData?.label || this.config.name || this.config.destinationId; + const slug = (name || '').toLowerCase().replace(/[^a-z0-9]+/g, '').replace(/(^-|-$)/g, '') || this.config.destinationId; + return { + ...this.buildBaseEntityObject(destinationData), + _id: `${this.config.destinationId}_destination`, + slug, + entityType: entityType.destination, + }; + } + + /** + * Build park entities for this destination + * In TE2, the destination and park are typically the same venue + * @return {Promise>} Array of park entity objects + */ + async buildParkEntities() { + const destinationData = await this.getDestinationData(); + const name = destinationData?.name || destinationData?.label || this.config.name || this.config.destinationId; + const slug = (name || '').toLowerCase().replace(/[^a-z0-9]+/g, '').replace(/(^-|-$)/g, '') || this.config.destinationId; + return [ + { + ...this.buildBaseEntityObject(destinationData), + _id: this.config.destinationId, + _destinationId: `${this.config.destinationId}_destination`, + _parentId: `${this.config.destinationId}_destination`, + slug: `${slug || this.config.destinationId}park`, + entityType: entityType.park, + }, + ]; + } + + /** + * Filter POI data to get entities matching specified types/IDs + * Supports custom filtering via includeFn callback + * @param {object} categoryData Object containing types and entities arrays + * @param {Array} categoryData.types Category type IDs to match + * @param {Array} categoryData.entities Entity IDs to match + * @param {object} data Additional data to merge into each entity + * @param {object} options Options object + * @param {Function} [options.includeFn] Custom filter function for additional inclusion logic + * @return {Promise>} Array of filtered entity objects + * @private + */ + async _getFilteredEntities({types, entities}, data, {includeFn} = {}) { + const poi = await this.getPOIData(); + if (!Array.isArray(poi)) { + return []; + } + + const typeSet = new Set(Array.isArray(types) ? types : []); + const entitySet = new Set(Array.isArray(entities) ? entities : []); + const seenIds = new Set(); + + return poi.filter((entry) => { + if (!entry?.id) return false; + + if (seenIds.has(entry.id)) { + return false; + } + + const matchesCategory = typeSet.has(entry.type) || entitySet.has(entry.id); + const matchesFallback = typeof includeFn === 'function' ? includeFn(entry) : false; + + if (!(matchesCategory || matchesFallback)) { + return false; + } + + seenIds.add(entry.id); + return true; + }).map((entry) => { + return { + ...this.buildBaseEntityObject(entry), + _destinationId: `${this.config.destinationId}_destination`, + _parentId: this.config.destinationId, + _parkId: this.config.destinationId, + ...data, + }; + }); + } + + /** + * Build attraction entities for this destination + * Includes rides identified by category or by presence of wait time/ride indicator tags + * @return {Promise>} Array of attraction entity objects + */ + async buildAttractionEntities() { + return await this._getFilteredEntities( + await this.getAttractionTypes(), + { + entityType: entityType.attraction, + attractionType: attractionType.ride, + }, + { + includeFn: (entry) => { + // Include entries with status data that have ride indicator tags + const status = entry?.status; + if (!status || (status.waitTime === undefined && status.operationalStatus === undefined)) { + return false; + } + + const tags = Array.isArray(entry?.tags) ? entry.tags : []; + return tags.some((tag) => { + const label = (tag?.label || '').toLowerCase(); + return RIDE_INDICATOR_LABELS.has(label); + }); + }, + }, + ); + } + + /** + * Build restaurant/dining entities for this destination + * @return {Promise>} Array of restaurant entity objects + */ + async buildRestaurantEntities() { + return await this._getFilteredEntities( + await this.getDiningTypes(), + { + entityType: entityType.restaurant, + }, + ); + } + + /** + * Build a POI lookup map from POI data array + * @param {Array} poiData - Array of POI data objects + * @return {Map} Map of POI ID to POI object + * @private + */ + _buildPOIMap(poiData) { + const poiMap = new Map(); + if (Array.isArray(poiData)) { + poiData.forEach((poi) => { + if (poi?.id) { + poiMap.set(poi.id, poi); + } + }); + } + return poiMap; + } + + /** + * Find location data from associated POIs or fallback sources + * @param {Array} associatedPois - Array of associated POI objects + * @param {object} basePoi - Base POI object to check for location + * @return {object|null} Location object with latitude/longitude or null + * @private + */ + _findLocationForShowEntity(associatedPois, basePoi) { + // First try to find location from associated POIs + const poiWithLocation = associatedPois.find((poi) => poi?.location?.latitude && poi?.location?.longitude); + if (poiWithLocation) { + return { + latitude: Number(poiWithLocation.location.latitude), + longitude: Number(poiWithLocation.location.longitude), + }; + } + + // Try base POI location + if (basePoi?.location) { + return {...basePoi.location}; + } + + // Fallback to config location + if (this.config?.location) { + return {...this.config.location}; + } + + return null; + } + + /** + * Build a show entity from event calendar data + * @param {object} event - Event data from calendar + * @param {Map} poiMap - Map of POI IDs to POI objects + * @return {object} Show entity object + * @private + */ + _buildShowEntityFromEvent(event, poiMap) { + const associatedPois = Array.isArray(event.associatedPois) ? event.associatedPois : []; + + // Find base POI from associated POIs + let basePoi = null; + for (const assoc of associatedPois) { + if (assoc?.id && poiMap.has(assoc.id)) { + basePoi = poiMap.get(assoc.id); + break; + } + } + + // Build base entity from POI or event data + const entityBase = basePoi + ? this.buildBaseEntityObject(basePoi) + : this.buildBaseEntityObject({id: event.id, name: event.title}); + + const entity = { + ...entityBase, + _id: event.id, + _destinationId: `${this.config.destinationId}_destination`, + _parentId: this.config.destinationId, + _parkId: this.config.destinationId, + entityType: entityType.show, + name: event.title || entityBase?.name || event.id, + }; + + // Add description if available + if (event.description) { + entity.description = event.description; + } + + // Find and set location if not already set + if (!entity.location) { + const location = this._findLocationForShowEntity(associatedPois, basePoi); + if (location) { + entity.location = location; + } + } + + return entity; + } + + /** + * Build show/entertainment entities for this destination + * Combines show entities from categories and event calendar data + * @return {Promise>} Array of show entity objects + */ + async buildShowEntities() { + // Get show entities from filtered show types + const showEntities = await this._getFilteredEntities( + await this.getShowTypes(), + { + entityType: entityType.show, + }, + ); + + const existingIds = new Set(showEntities.map((ent) => ent?._id).filter((id) => !!id)); + + // Fetch event calendar data and add any missing show entities + const {events, showtimesByEvent} = await this._getEventCalendarData(); + if (events.length > 0) { + const poiData = await this.getPOIData(); + const poiMap = this._buildPOIMap(poiData); + + events.forEach((event) => { + if (!event?.id) return; + if (existingIds.has(event.id)) return; + + const entity = this._buildShowEntityFromEvent(event, poiMap); + showEntities.push(entity); + existingIds.add(entity._id); + }); + } + + return showEntities; + } + + /** + * Fetch venue operating hours schedule data + * @param {object} options Options object + * @param {number} [options.days=120] Number of days to fetch schedule for + * @return {Promise} Schedule data with daily operating hours + * @private + */ + async _fetchScheduleData({days = 120} = {}) { + '@cache|1440'; + const resp = await this.http('GET', `${this.config.apiBase}/v2/venues/${this.config.venueId}/venue-hours?days=${days}`); + return resp.body; + } + + /** + * Fetch event calendar data including shows and entertainment schedules + * @param {object} options Options object + * @param {number} [options.days] Number of days to fetch (uses config.eventScheduleDays if not specified) + * @return {Promise} Event calendar data with events and schedules + */ + async fetchEventCalendar({days} = {}) { + '@cache|30'; + const duration = Number.isFinite(days) ? days : this.config.eventScheduleDays; + const resp = await this.http('GET', `${this.config.apiBase}/v2/venues/${this.config.venueId}/calendars/events?days=${duration}`); + return resp?.body ?? resp; + } + + /** + * Get parsed event calendar data with showtimes organized by event + * Filters out past events and creates showtime objects + * @return {Promise} Object containing events, eventsById map, and showtimesByEvent map + * @private + */ + async _getEventCalendarData() { + try { + const calendar = await this.fetchEventCalendar({}); + const events = Array.isArray(calendar?.events) ? calendar.events : []; + const schedules = Array.isArray(calendar?.schedules) ? calendar.schedules : []; + if (!events.length || !schedules.length) { + return { + events: [], + eventsById: new Map(), + showtimesByEvent: new Map(), + }; + } + + const eventsById = new Map(); + events.forEach((event) => { + if (event?.id) { + eventsById.set(event.id, event); + } + }); + + const nowMs = Date.now(); + const showtimesByEvent = new Map(); + + schedules.forEach((slot) => { + const event = eventsById.get(slot?.eventId); + if (!event) return; + + const start = slot?.start; + if (!start) return; + + const startMs = Date.parse(start); + if (!Number.isFinite(startMs) || startMs < nowMs) return; + + const end = slot?.end; + const endMs = end ? Date.parse(end) : Number.NaN; + const showtime = { + type: SCHEDULE_TYPE_PERFORMANCE, + startTime: start, + endTime: Number.isFinite(endMs) ? end : start, + }; + + if (!showtimesByEvent.has(event.id)) { + showtimesByEvent.set(event.id, new Map()); + } + + const eventMap = showtimesByEvent.get(event.id); + const key = `${showtime.startTime}|${showtime.endTime}`; + if (!eventMap.has(key)) { + eventMap.set(key, showtime); + } + }); + + const normalizedShowtimes = new Map(); + showtimesByEvent.forEach((eventMap, eventId) => { + const items = Array.from(eventMap.values()).sort((a, b) => a.startTime.localeCompare(b.startTime)); + if (items.length > 0) { + normalizedShowtimes.set(eventId, items); + } + }); + + return { + events, + eventsById, + showtimesByEvent: normalizedShowtimes, + }; + } catch (err) { + this.log(`Failed to fetch TE2 event data: ${err?.message || err}`); + return { + events: [], + eventsById: new Map(), + showtimesByEvent: new Map(), + }; + } + } + + async buildEntityScheduleData() { + const scheduleData = await this._fetchScheduleData(); + if (!Array.isArray(scheduleData?.days)) { + return []; + } + + const scheduleEntries = []; + scheduleData.days.forEach((day) => { + (day.hours || []).forEach((hours) => { + if (day.label !== 'Park' && hours.status === 'CLOSED') return; + + const start = hours?.schedule?.start; + const end = hours?.schedule?.end; + if (!start || !end) return; + + const startMoment = moment(start).tz(this.config.timezone); + const endMoment = moment(end).tz(this.config.timezone); + if (!startMoment.isValid() || !endMoment.isValid()) return; + + const label = typeof hours.label === 'string' ? hours.label.trim() : ''; + const normalizedLabel = label.toLowerCase(); + + // Determine if this is a park operating schedule or informational schedule + let scheduleTypeValue = scheduleType.informational; + if (PARK_SCHEDULE_LABELS.includes(normalizedLabel)) { + scheduleTypeValue = scheduleType.operating; + } + + scheduleEntries.push({ + date: startMoment.format('YYYY-MM-DD'), + type: scheduleTypeValue, + description: normalizedLabel === 'park' ? undefined : label || undefined, + openingTime: startMoment.format(), + closingTime: endMoment.format(), + }); + }); + }); + + if (scheduleEntries.length === 0) { + return []; + } + + scheduleEntries.sort((a, b) => { + const dateCompare = a.date.localeCompare(b.date); + if (dateCompare !== 0) { + return dateCompare; + } + return a.openingTime.localeCompare(b.openingTime); + }); + + return [ + { + _id: this.config.destinationId, + schedule: scheduleEntries, + }, + ]; + } + + async buildEntityLiveData() { + const liveDataMap = new Map(); + + const statusData = await this.getPOIStatus(); + if (Array.isArray(statusData)) { + statusData.forEach((entry) => { + if (!entry?.status || !entry.id) return; + if (entry.id.includes('_STANDING_OFFER_BEACON')) return; + + const liveData = liveDataMap.get(entry.id) || {_id: entry.id}; + + if (entry.status.waitTime !== undefined) { + const rawWait = Number(entry.status.waitTime); + const sanitizedWait = Number.isFinite(rawWait) ? Math.max(0, Math.round(rawWait)) : null; + + liveData.queue = { + [queueType.standBy]: { + waitTime: sanitizedWait, + }, + }; + } + + liveData.status = entry.status.isOpen ? statusType.operating : statusType.closed; + + liveDataMap.set(entry.id, liveData); + }); + } + + const {eventsById, showtimesByEvent} = await this._getEventCalendarData(); + const now = this.getTimeNowMoment(); + const todayKey = now.tz(this.config.timezone).format('YYYY-MM-DD'); + + const filterShowtimesForToday = (showtimes) => { + return showtimes.filter((slot) => { + if (!slot?.startTime) return false; + const startMoment = moment.tz(slot.startTime, this.config.timezone); + if (!startMoment.isValid()) return false; + if (startMoment.isBefore(now)) return false; + return startMoment.format('YYYY-MM-DD') === todayKey; + }); + }; + + showtimesByEvent.forEach((showtimes, eventId) => { + if (!showtimes.length) return; + const todaysShowtimes = filterShowtimesForToday(showtimes); + if (!todaysShowtimes.length) return; + + const liveData = liveDataMap.get(eventId) || {_id: eventId}; + liveData.showtimes = todaysShowtimes; + liveData.status = statusType.operating; + + liveDataMap.set(eventId, liveData); + }); + + return Array.from(liveDataMap.values()); + } +} + +export class VrtpSeaWorldTe2 extends TE2Destination { + constructor(options = {}) { + options.name = options.name || 'Sea World (TE2)'; + options.destinationId = options.destinationId || 'vrtp_sw_te2'; + options.venueId = options.venueId || 'VRTP_SW'; + super(options); + } +} + +export class VrtpMovieWorldTe2 extends TE2Destination { + constructor(options = {}) { + options.name = options.name || 'Warner Bros. Movie World (TE2)'; + options.destinationId = options.destinationId || 'vrtp_mw_te2'; + options.venueId = options.venueId || 'VRTP_MW'; + super(options); + } +} From c8dc6e6dbaef57946c6f7c1f2c97b752183fcbf7 Mon Sep 17 00:00:00 2001 From: Lachlan Mason Date: Sat, 8 Nov 2025 23:44:21 +1100 Subject: [PATCH 2/4] updated imports and names --- lib/index.js | 21 +++++++++++++-------- lib/parks/te2/te2.js | 8 ++++---- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/index.js b/lib/index.js index f2a89db0..0190ca6d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -124,6 +124,10 @@ import { import { Futuroscope, } from './parks/futuroscope/futuroscope.js'; +import { + SeaWorldGoldCoast, + WarnerBrosMovieWorld, +} from './parks/te2/te2.js'; export default { destinations: { @@ -189,13 +193,14 @@ export default { UniversalStudiosBeijing, Chimelong, Everland, - LotteWorld, - MovieParkGermany, - Bobbejaanland, - Mirabilandia, - ParqueDeAtraccionesMadrid, - ParqueWarnerMadrid, - Kennywood, - Futuroscope, + LotteWorld, + MovieParkGermany, + Bobbejaanland, + Mirabilandia, + ParqueDeAtraccionesMadrid, + ParqueWarnerMadrid, + Kennywood, + Futuroscope, + WarnerBrosMovieWorld, }, }; diff --git a/lib/parks/te2/te2.js b/lib/parks/te2/te2.js index fbe4130d..cf200a36 100644 --- a/lib/parks/te2/te2.js +++ b/lib/parks/te2/te2.js @@ -720,18 +720,18 @@ export class TE2Destination extends Destination { } } -export class VrtpSeaWorldTe2 extends TE2Destination { +export class SeaWorldGoldCoast extends TE2Destination { constructor(options = {}) { - options.name = options.name || 'Sea World (TE2)'; + options.name = options.name || 'Sea World Gold Coast'; options.destinationId = options.destinationId || 'vrtp_sw_te2'; options.venueId = options.venueId || 'VRTP_SW'; super(options); } } -export class VrtpMovieWorldTe2 extends TE2Destination { +export class WarnerBrosMovieWorld extends TE2Destination { constructor(options = {}) { - options.name = options.name || 'Warner Bros. Movie World (TE2)'; + options.name = options.name || 'Warner Bros. Movie World'; options.destinationId = options.destinationId || 'vrtp_mw_te2'; options.venueId = options.venueId || 'VRTP_MW'; super(options); From eb40532ad6fa03abe955b6802cfb42052c899068 Mon Sep 17 00:00:00 2001 From: technodisney <40648435+technodisney@users.noreply.github.com> Date: Sat, 8 Nov 2025 13:11:05 +0000 Subject: [PATCH 3/4] adjusted formatting --- lib/index.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/index.js b/lib/index.js index 0190ca6d..2814a16b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -193,14 +193,14 @@ export default { UniversalStudiosBeijing, Chimelong, Everland, - LotteWorld, - MovieParkGermany, - Bobbejaanland, - Mirabilandia, - ParqueDeAtraccionesMadrid, - ParqueWarnerMadrid, - Kennywood, - Futuroscope, - WarnerBrosMovieWorld, + LotteWorld, + MovieParkGermany, + Bobbejaanland, + Mirabilandia, + ParqueDeAtraccionesMadrid, + ParqueWarnerMadrid, + Kennywood, + Futuroscope, + WarnerBrosMovieWorld, }, }; From 1f154d77f4197e0a5bd4e6bfb2ebb8df1b00c9d9 Mon Sep 17 00:00:00 2001 From: technodisney <40648435+technodisney@users.noreply.github.com> Date: Sat, 8 Nov 2025 13:13:09 +0000 Subject: [PATCH 4/4] Added SeaWorld to index --- lib/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/index.js b/lib/index.js index 2814a16b..85790388 100644 --- a/lib/index.js +++ b/lib/index.js @@ -202,5 +202,6 @@ export default { Kennywood, Futuroscope, WarnerBrosMovieWorld, + SeaWorldGoldCoast, }, };