### Start Live Database Replication (JavaScript) Source: https://themeparks.github.io/parksapi/parks_wdw_wdwdb.js Starts a live replication process between a remote database and the local instance. It includes logic to automatically restart replication on errors or timeouts. The replication is configured to be live and to retry on connection failures. Dependencies include PouchDB and timers for managing timeouts. ```javascript _replicate() { if (this.replicating) return; this.replicating = true; let noChangeTimeout = null; let replicationHandle = null; const noChangeTimer = Number(this.config.restartTimeout) || 5; const rebootReplicator = (err) => { if (noChangeTimeout) { clearTimeout(noChangeTimeout); } if (replicationHandle) { replicationHandle.cancel(); replicationHandle = null; } if (err) { console.error('Replication Error!', new Date(), err); } this.replicating = false; setTimeout(this._replicate.bind(this), 1000); }; const resetTimeoutTimer = () => { if (noChangeTimeout) { clearTimeout(noChangeTimeout); } noChangeTimeout = setTimeout(() => { rebootReplicator(); }, 1000 * 60 * noChangeTimer); }; try { replicationHandle = PouchDB.replicate(this.remoteDB, this, { live: true, retry: true, }).on('change', () => { resetTimeoutTimer(); }).on('error', (e) => { rebootReplicator(e); }); resetTimeoutTimer(); } catch (e) { rebootReplicator(e); } } ``` -------------------------------- ### Initialize Park and Start Update Loop (JavaScript) Source: https://themeparks.github.io/parksapi/parks_park.js Initializes the park object and, if not disabled and online, starts a recurring update loop. The loop schedules updates at a defined interval, ensuring data is kept current. It handles potential errors during initialization and update processes. ```javascript async _runInit() { try { await this._init(); this.initialised = true; if (!this.config.disableParkUpdate && !this.offline) { // start an update loop // use a separate function so we can quickly loop back around const scheduleUpdate = async () => { // pause for our updateInterval time await delay(this.config.updateInterval); // if our udpates get disabled during our timer, then skip and exit our if (this.config.disableParkUpdate) return; // wait for Promise to resolve, grab any catches, then continue anyway this.update().then().catch().then(() => { if (this.config.disableParkUpdate) return; // schedule another update setImmediate(scheduleUpdate.bind(this)); }); }; // start the first loop timer scheduleUpdate(); } } catch (e) { console.error('Error initialising park', e); } } ``` -------------------------------- ### Abstract Post-Initialization Method (_postInit) in JavaScript Source: https://themeparks.github.io/parksapi/parks_park.js This abstract JavaScript method `_postInit` is designed for child classes to override. It is called after the main initialization process, likely for additional setup tasks. It currently has no implementation and is intended for extension. ```javascript async _postInit() { // implementation should be setup in child classes } ``` -------------------------------- ### Get Entity Live Data - JavaScript Source: https://themeparks.github.io/parksapi/parks_destination.js Retrieves and processes all live data for entities. It first initializes the system, then calls `buildEntityLiveData` to get the data. It iterates through the generated live data, updating each entity's live data and handling potential errors during the update process. ```javascript async getEntityLiveData() { await this.init(); const liveData = (await this.buildEntityLiveData()) || []; // process all live data we generated for (let liveDataIdx = 0; liveDataIdx < liveData.length; liveDataIdx++) { const data = liveData[liveDataIdx]; try { await this.updateEntityLiveData(data._id, data); } catch (e) { // if (!e instanceof EntityNotFound) { console.error(`Failed to apply live data to ${data._id}`); console.error(e); // } // 19411262;entityType=Attraction = Pop/Art Skyliner Line // 19404062;entityType=Attraction = Hollywood Studios Skyliner Line // 19404065;entityType=Attraction = Epcot Skyliner Line } } return liveData; } ``` -------------------------------- ### Helper Function for GET Requests (JavaScript) Source: https://themeparks.github.io/parksapi/parks_http.js A convenience function to simplify making HTTP GET requests. It takes a URL, optional data payload, and optional request options, then calls the main HTTP function. ```javascript /** * Helper function to make a GET request * @param {string} url URL to request * @param {object} [data] data to send. Will become querystring for GET, body for POST * @param {object} [options = {}] Object containing needle-compatible HTTP options * @return {Promise<*>} */ mainFunction.get = (url, data, options) => { return mainFunction('GET', url, data, options); }; ``` -------------------------------- ### Get Europa Park Live Opening Hours Source: https://themeparks.github.io/parksapi/parks_europa_europaparkdb.js Retrieves live opening hours for Europa Park. This data is cached for 5 minutes to provide up-to-date information without excessive API calls. ```javascript async getLiveCalendar() { return this.cache.wrap('livecalendar', async () => { return (await this.http('GET', `${this.config.apiBase}/api/v2/season-opentime-details/europapark`)).body; }, 1000 * 60 * 5); // cache for 5 minutes } ``` -------------------------------- ### Get Europa Park Show Times Source: https://themeparks.github.io/parksapi/parks_europa_europaparkdb.js Fetches show times for Europa Park. The data is cached for 6 hours. Currently, it retrieves show times for 'live' status. Further consideration is needed for handling different languages. ```javascript async getShowTimes() { return this.cache.wrap('showtimes', async () => { // TODO - other languages? does this only include English performances? return (await this.http('GET', `${this.config.apiBase}/api/v2/show-times`, { status: 'live', })).body; }, 1000 * 60 * 60 * 6); } ``` -------------------------------- ### ConfigBase Constructor Source: https://themeparks.github.io/parksapi/ConfigBase Initializes a new ConfigBase instance, allowing configuration through an options object and supporting environment variable overrides. ```APIDOC ## new ConfigBase(options) ### Description A base class that can be configured through environment variables. ### Method `new` ### Endpoint N/A (Constructor) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```json { "options": { "timeout": 5000, "retries": 3 } } ``` ### Response #### Success Response (200) N/A (Constructor) #### Response Example N/A (Constructor) ``` -------------------------------- ### Get Active Park Date as String (JavaScript) Source: https://themeparks.github.io/parksapi/parks_park.js Retrieves the 'active' park date and formats it as a 'YYYY-MM-DD' string. This function internally calls 'getActiveParkDateMoment' to get the Moment object representation of the date and then formats it. ```javascript async getActiveParkDate() { return (await this.getActiveParkDateMoment()).format('YYYY-MM-DD'); } ``` -------------------------------- ### TE2Destination Constructor Source: https://themeparks.github.io/parksapi/TE2Destination Initializes a new TE2Destination object with configuration options. It handles basic authentication and allows customization of various park-related settings. ```APIDOC ## TE2Destination Constructor ### Description Create a new TE2Destination object. ### Method `new TE2Destination(options)` ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body * **options** (object) - Configuration options #### Properties * **timezone** (string) - Optional - Park timezone (default: Australia/Brisbane) * **destinationId** (string) - Optional - Unique destination identifier * **venueId** (string) - Optional - TE2 API venue ID * **subdomain** (string) - Optional - TE2 API subdomain * **apidomain** (string) - Optional - TE2 API domain * **apiuser** (string) - Optional - TE2 API username * **apipass** (string) - Optional - TE2 API password * **rideTypes** (Array.) - Optional - Categories to classify as rides * **diningTypes** (Array.) - Optional - Categories to classify as dining * **showTypes** (Array.) - Optional - Categories to classify as shows * **eventScheduleDays** (number) - Optional - Days to fetch for event schedule (default: 14) ### Request Example ```json { "timezone": "America/New_York", "destinationId": "DLP", "venueId": "12345", "subdomain": "disneyland", "apidomain": "api.example.com", "apiuser": "user", "apipass": "password", "rideTypes": ["Coaster", "Thrill Ride"], "diningTypes": ["Restaurant", "Quick Service"], "showTypes": ["Show", "Parade"], "eventScheduleDays": 30 } ``` ### Response #### Success Response (200) N/A (Constructor does not return a value) #### Response Example N/A ``` -------------------------------- ### Initialize and Sync Database (JavaScript) Source: https://themeparks.github.io/parksapi/parks_wdw_wdwdb.js Initializes the database, performing an initial synchronization if required. It handles potential race conditions by caching the initialization promise. The function also starts live replication if configured and a remote database is available. Dependencies include PouchDB for replication and console for logging. ```javascript async init() { if (this.synced) { return; } if (this.initPromiseSync) return this.initPromiseSync; this.initPromiseSync = this._loadAndInit(); await this.initPromiseSync; this.initPromiseSync = null; console.log(`Database finished setup!`); this.synced = true; if (!this.config.skipSync && this.remoteDB) { this._replicate(); } } ``` -------------------------------- ### Get Park Calendar for Today - JavaScript Source: https://themeparks.github.io/parksapi/parks_park.js Retrieves the park's calendar information for the current active date. It depends on methods to get the active park date and the full calendar data. It returns the calendar entry for the current date. ```javascript async getCalendarForToday() { const todaysDate = await this.getActiveParkDate(); const calendar = await this.getCalendar(); return calendar[todaysDate]; } ``` -------------------------------- ### Get Documents by IDs using Promise.all Source: https://themeparks.github.io/parksapi/parks_wdw_wdwdb.js Fetches multiple documents by their IDs concurrently using Promise.all. It maps each ID to a 'get' operation and then filters out any undefined results, which may occur if a document has been deleted. This method is asynchronous and requires an initialized instance. ```javascript async getDocsById(ids) { await this.init(); return (await Promise.all(ids.map((id) => { // fetch each document using our local DB return this.get(id); }))).filter((doc) => { // filter our any docs that failed to be fetched (they have been deleted etc.) return doc !== undefined; }); } ``` -------------------------------- ### Show Types API Source: https://themeparks.github.io/parksapi/TE2Destination Get parsed category data for show/entertainment types. ```APIDOC ## GET /websites/themeparks_github_io_parksapi/getShowTypes ### Description Get parsed category data for show/entertainment types. ### Method GET ### Endpoint /websites/themeparks_github_io_parksapi/getShowTypes ### Response #### Success Response (200) - **object** - Object with types array and entities array. #### Response Example { "types": ["Musical", "Parade", "Stage Show"], "entities": [{"id": "show_1", "name": "Fantasmic!"}] } ``` -------------------------------- ### Efteling Park API Client Setup (JavaScript) Source: https://themeparks.github.io/parksapi/parks_efteling_efteling.js Initializes the Efteling park API client, setting up base URLs, required API credentials (apiKey, apiVersion, appVersion), and injecting custom headers for all API requests. This includes adding app-specific headers for Android and overriding the user-agent. ```javascript import {attractionType, statusType, queueType, tagType, scheduleType, entityType, returnTimeState} from '../parkTypes.js'; import moment from 'moment-timezone'; import Destination from '../destination.js'; import {fileURLToPath} from 'url'; import {dirname, join as pathJoin} from 'path'; import {promises as fs} from 'fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export class Efteling extends Destination { constructor(options = {}) { options.name = options.name || 'Efteling'; options.timezone = options.timezone || 'Europe/Amsterdam'; options.apiKey = options.apiKey || ''; options.apiVersion = options.apiVersion || ''; options.appVersion = options.appVersion || ''; options.searchUrl = options.searchUrl || 'https://prd-search-acs.efteling.com/2013-01-01/search'; options.waitTimesUrl = options.waitTimesUrl || 'https://api.efteling.com/app/wis/'; options.virtualQueueWindowMinutes = options.virtualQueueWindowMinutes || 15; options.cacheVersion = 1; super(options); if (!this.config.apiKey) throw new Error('Missing Efteling apiKey'); if (!this.config.apiVersion) throw new Error('Missing Efteling apiVersion'); if (!this.config.appVersion) throw new Error('Missing Efteling appVersion'); this.http.injectForDomain({ $or: [ { hostname: 'api.efteling.com', }, { hostname: 'prd-search-acs.efteling.com', }, { hostname: 'cloud.efteling.com', } ], }, (method, url, data, options) => { options.headers['x-app-version'] = this.config.appVersion; options.headers['x-app-name'] = 'Efteling'; options.headers['x-app-id'] = 'nl.efteling.android'; options.headers['x-app-platform'] = 'Android'; options.headers['x-app-language'] = 'en'; options.headers['x-app-timezone'] = this.config.timezone; options.headers['user-agent'] = 'okhttp/4.12.0'; options.compressed = true; }); this.http.injectForDomain({ hostname: 'api.efteling.com', }, (method, url, data, options) => { options.headers['x-api-key'] = this.config.apiKey; options.headers['x-api-version'] = this.config.apiVersion; }); } ``` -------------------------------- ### Dining Types API Source: https://themeparks.github.io/parksapi/TE2Destination Get parsed category data for dining types. ```APIDOC ## GET /websites/themeparks_github_io_parksapi/getDiningTypes ### Description Get parsed category data for dining types. ### Method GET ### Endpoint /websites/themeparks_github_io_parksapi/getDiningTypes ### Response #### Success Response (200) - **object** - Object with types array and entities array. #### Response Example { "types": ["Quick Service", "Table Service"], "entities": [{"id": "dining_1", "name": "Pizza Palace"}] } ``` -------------------------------- ### Load and Initialize Database with Initial Sync (JavaScript) Source: https://themeparks.github.io/parksapi/parks_wdw_wdwdb.js Performs an initial load and sync for the database. It first initializes any necessary indexes, then reindexes all existing documents. Finally, it initiates a one-time replication from a remote database if synchronization is not skipped. Dependencies include PouchDB for replication and internal methods for index management. ```javascript async _loadAndInit() { await this._initIndexes(); const docs = await this.allDocs({ include_docs: true, }); console.log('Building index...'); await Promise.allSettled(docs.rows.map((doc) => { return this._indexWDWDocument(doc.doc); })); if (this.config.skipSync || !this.remoteDB) { return; } console.log('Performing initial replication...'); return await PouchDB.replicate(this.remoteDB, this, { batch_size: 500, }).catch((e) => { console.error(`Replication error: ${e}`); }); } ``` -------------------------------- ### TE2Destination Private Methods Source: https://themeparks.github.io/parksapi/TE2Destination Documentation for the private methods within the TE2Destination class, including data processing and fetching utilities. ```APIDOC ## TE2Destination Private Methods ### _buildPOIMap(poiData) #### Description Build a POI lookup map from POI data array. #### Method `(private) _buildPOIMap(poiData)` #### Endpoint N/A (Internal method) #### Parameters ##### Path Parameters None ##### Query Parameters None ##### Request Body None #### Parameters * **poiData** (Array) - Array of POI data objects ### Request Example ```json [ { "id": "poi1", "name": "Example Attraction" }, { "id": "poi2", "name": "Example Restaurant" } ] ``` ### Response #### Success Response (200) * **Map** - Map of POI ID to POI object #### Response Example ```json { "poi1": { "id": "poi1", "name": "Example Attraction" }, "poi2": { "id": "poi2", "name": "Example Restaurant" } } ``` --- ### _buildShowEntityFromEvent(event, poiMap) #### Description Build a show entity from event calendar data. #### Method `(async, private) _buildShowEntityFromEvent(event, poiMap)` #### Endpoint N/A (Internal method) #### Parameters ##### Path Parameters None ##### Query Parameters None ##### Request Body None #### Parameters * **event** (object) - Event data from calendar * **poiMap** (Map) - Map of POI IDs to POI objects ### Request Example ```json { "event": { "id": "event1", "name": "Evening Show", "poiId": "poi1" }, "poiMap": { "poi1": { "id": "poi1", "name": "Show Venue" } } } ``` ### Response #### Success Response (200) * **object** - Show entity object #### Response Example ```json { "id": "event1", "name": "Evening Show", "location": { "id": "poi1", "name": "Show Venue" } } ``` --- ### _fetchCategories() #### Description Fetch category definitions from the TE2 API. #### Method `(async, private) _fetchCategories()` #### Endpoint N/A (Internal method) #### Parameters None ### Request Example None ### Response #### Success Response (200) * **Promise.** - Category data with POI associations #### Response Example ```json { "categories": [ { "id": "cat1", "name": "Rides", "entityIds": ["ride1", "ride2"] } ], "entities": [ { "id": "ride1", "name": "Roller Coaster" } ] } ``` --- ### _fetchScheduleData(options) #### Description Fetch venue operating hours schedule data. #### Method `(async, private) _fetchScheduleData(options)` #### Endpoint N/A (Internal method) #### Parameters ##### Path Parameters None ##### Query Parameters None ##### Request Body None #### Parameters * **options** (object) - Options object #### Properties * **days** (number) - Optional - Number of days to fetch schedule for (default: 120) ### Request Example ```json { "days": 30 } ``` ### Response #### Success Response (200) * **Promise.** - Schedule data with daily operating hours #### Response Example ```json { "schedule": [ { "date": "2023-10-27", "open": "09:00", "close": "21:00" } ] } ``` --- ### _findLocationForShowEntity(associatedPois, basePoi) #### Description Find location data from associated POIs or fallback sources. #### Method `(async, private) _findLocationForShowEntity(associatedPois, basePoi)` #### Endpoint N/A (Internal method) #### Parameters ##### Path Parameters None ##### Query Parameters None ##### Request Body None #### Parameters * **associatedPois** (Array) - Array of associated POI objects * **basePoi** (object) - Base POI object to check for location ### Request Example ```json { "associatedPois": [ { "id": "poi1", "name": "Associated Venue" } ], "basePoi": { "id": "basePoi1", "name": "Main Area" } } ``` ### Response #### Success Response (200) * **object|null** - Location object with latitude/longitude or null #### Response Example ```json { "latitude": 33.8121, "longitude": -117.9190 } ``` --- ### _getEventCalendarData() #### Description Get parsed event calendar data with showtimes organized by event. Filters out past events and creates showtime objects. #### Method `(async, private) _getEventCalendarData()` #### Endpoint N/A (Internal method) #### Parameters None ### Request Example None ### Response #### Success Response (200) * **Promise.** - Object containing events, eventsById map, and showtimesByEvent map #### Response Example ```json { "events": [ { "id": "event1", "name": "Daily Parade", "showtimes": ["10:00", "15:00"] } ], "eventsById": { "event1": { "id": "event1", "name": "Daily Parade" } }, "showtimesByEvent": { "event1": ["10:00", "15:00"] } } ``` --- ### _getFilteredEntities(categoryData, data, options) #### Description Filter POI data to get entities matching specified types/IDs. Supports custom filtering via includeFn callback. #### Method `(async, private) _getFilteredEntities(categoryData, data, options)` #### Endpoint N/A (Internal method) #### Parameters ##### Path Parameters None ##### Query Parameters None ##### Request Body None #### Parameters * **categoryData** (object) - Object containing types and entities arrays #### Properties * **types** (Array.) - Category type IDs to match * **entities** (Array.) - Entity IDs to match * **data** (object) - Additional data to merge into each entity * **options** (object) - Options object #### Properties * **includeFn** (function) - Optional - Custom filter function for additional inclusion logic ### Request Example ```json { "categoryData": { "types": ["ride"], "entities": ["ride1"] }, "data": { "park": "Magic Kingdom" }, "options": { "includeFn": "(entity) => entity.capacity > 1000" } } ``` ### Response #### Success Response (200) * **Promise.>** - Array of filtered entity objects #### Response Example ```json [ { "id": "ride1", "name": "Big Mountain Coaster", "park": "Magic Kingdom" } ] ``` --- ### _getParsedCategories(params) #### Description Parse category data to find POI entities matching the given types. Recursively includes child categories based on parent relationships. #### Method `(async, private) _getParsedCategories(params)` #### Endpoint N/A (Internal method) #### Parameters ##### Path Parameters None ##### Query Parameters None ##### Request Body None #### Parameters * **params** (object) - Parameters object (details not specified in source) ### Request Example ```json { "types": ["attraction", "dining"], "includeChildren": true } ``` ### Response #### Success Response (200) * **Promise.** - Parsed category data with matching entities #### Response Example ```json { "ride": [ { "id": "ride1", "name": "Roller Coaster" } ], "dining": [ { "id": "restaurant1", "name": "Themed Restaurant" } ] } ``` ``` -------------------------------- ### Attraction Types API Source: https://themeparks.github.io/parksapi/TE2Destination Get parsed category data for attraction/ride types. ```APIDOC ## GET /websites/themeparks_github_io_parksapi/getAttractionTypes ### Description Get parsed category data for attraction/ride types. ### Method GET ### Endpoint /websites/themeparks_github_io_parksapi/getAttractionTypes ### Response #### Success Response (200) - **object** - Object with types array and entities array. #### Response Example { "types": ["Thrill Ride", "Family Ride"], "entities": [{"id": "ride_1", "name": "The Great Coaster"}] } ``` -------------------------------- ### Initialize and Create Indexes (JavaScript) Source: https://themeparks.github.io/parksapi/parks_wdw_wdwdb.js Manages the initialization of database indexes. The `_initIndexes` function ensures that indexes are created only once by checking internal flags and promises. The `_createIndexes` function calls individual index creation methods for various index types like entities, channels, and facility status. ```javascript async _initIndexes() { if (this._indexSetup) { return; } if (this._setupPromise) { return this._setupPromise; } this._setupPromise = this._createIndexes(); await this._setupPromise; this._indexSetup = true; return; } ``` ```javascript async _createIndexes() { await this._createIndex(constants.INDEX_ENTITIES); await this._createIndex(constants.INDEX_CHANNELS); await this._createIndex(constants.INDEX_FACILITYSTATUS, []); await this._createIndex(constants.INDEX_CHILDREN); await this._createIndex(constants.INDEX_NAMES); await this._createIndex(constants.INDEX_IDS, []); } ``` -------------------------------- ### JavaScript: Initialize AttractionsIO with Configuration and API Hooks Source: https://themeparks.github.io/parksapi/parks_attractionsio_attractionsio.js Initializes the AttractionsIO class with essential configuration parameters like destination ID, park ID, base URL, API key, and app details. It sets up API request hooks to automatically include authentication headers and the current date for every request made to the Attractions.io API. ```javascript import {Destination} from '../destination.js'; import {attractionType, statusType, queueType, tagType, scheduleType, entityType} from '../parkTypes.js'; import {v4 as uuidv4} from 'uuid'; import moment from 'moment'; import unzip from 'yauzl'; import {promisify} from 'util'; import * as cheerio from 'cheerio'; const unzipFromBuffer = promisify(unzip.fromBuffer); const langs = ['en-GB', 'en-US', 'en-AU', 'en-CA', 'es-419', 'de-DE', 'it']; /** * Helper to extract name from a variable * @param {String|Object} name * @returns {String} */ function extractName(name) { if (typeof name === 'object') { // if we have translations, pick in priority order... const langIdx = langs.findIndex((lang) => !!name[lang]); if (langIdx > -1) { return name[langs[langIdx]].trim(); } else { // otherwise just pick the first one return Object.values(name)[0].trim(); } } return name.trim(); } export class AttractionsIO extends Destination { constructor(options = {}) { options.destinationId = options.destinationId || ''; options.parkId = options.parkId || ''; options.baseURL = options.baseURL || ''; options.timezone = options.timezone || 'Europe/London'; options.appBuild = options.appBuild || undefined; options.appVersion = options.appVersion || ''; options.deviceIdentifier = options.deviceIdentifier || '123'; options.apiKey = options.apiKey || ''; options.initialDataVersion = options.initialDataVersion || undefined; options.calendarURL = options.calendarURL || ''; // allow env config for all attractionsio destinations options.configPrefixes = ['ATTRACTIONSIO']; // invalidate cache options.cacheVersion = options.cacheVersion || '3'; super(options); if (!this.config.destinationId) throw new Error('destinationId is required'); if (!this.config.parkId) throw new Error('parkId is required'); if (!this.config.baseURL) throw new Error('Missing attractions.io base URL'); if (!this.config.appBuild) throw new Error('Missing appBuild'); if (!this.config.appVersion) throw new Error('Missing appVersion'); if (!this.config.deviceIdentifier) throw new Error('Missing deviceIdentifier'); if (!this.config.apiKey) throw new Error('Missing apiKey'); if (!this.config.calendarURL) throw new Error('Missing calendarURL'); // API hooks for auto-login const baseURLHostname = new URL(this.config.baseURL).hostname; // login when accessing API domain this.http.injectForDomain({ hostname: baseURLHostname, }, async (method, url, data, options) => { // always include the current date options.headers.date = moment().format(); if (options.skipDeviceId) { // special case for initial device setup options.headers['authorization'] = `Attractions-Io api-key="${this.config.apiKey}"`; return; } const deviceId = await this.getDeviceId(); options.headers['authorization'] = `Attractions-Io api-key="${this.config.apiKey}", installation-token="${deviceId}"`; }); } /** * Create a device ID to login to the API */ async getDeviceId() { '@cache|481801'; // cache 11 months const deviceId = uuidv4(); const resp = await this.http('POST', `${this.config.baseURL}installation`, { user_identifier: deviceId, app_build: this.config.appBuild, app_version: this.config.appVersion, device_identifier: this.config.deviceIdentifier, }, { skipDeviceId: true, }); return resp.body.token; } /** * Get POI data for this destination */ async getPOIData(depth = 0) { '@cache|720'; // cache for 12 hours // get current data asset version const currentParkDataVersion = (await this.cache.get('currentParkDataVersion')) || this.config.initialDataVersion; const dataQueryOptions = {}; if (currentParkDataVersion) { dataQueryOptions.version = currentParkDataVersion; } // query current data version const dataVersionQuery = await this.http( 'GET', `${this.config.baseURL}data`, Object.keys(dataQueryOptions).length > 0 ? dataQueryOptions : undefined, { ``` -------------------------------- ### Europa Park Database Class Initialization and HTTP Interceptors (JavaScript) Source: https://themeparks.github.io/parksapi/parks_europa_europaparkdb.js Initializes the Europa Park database connection, setting up required API credentials, encryption keys, and configuring HTTP interceptors. These interceptors automatically inject the correct user agent and JWT authorization headers for API requests and handle token refresh on unauthorized responses. ```javascript import Database from '../database.js'; import crypto from 'crypto'; import {URL} from 'url'; import {tagType, attractionType, entityType} from '../parkTypes.js'; import {Blowfish} from 'egoroof-blowfish'; const poiEntityTypes = [ 'attraction', 'showlocation', 'shows', 'pois', ]; const entityTypeToAttractionType = { 'shows': entityType.show, 'pois': entityType.attraction, 'attraction': entityType.attraction, 'showlocation': entityType.show, }; const subtypesToAllow = { 'pois': [ 'attraction', ], }; /** * Europa Park Database Class */ export class DatabaseEuropaPark extends Database { /** * @inheritdoc * @param {object} options */ constructor(options = {}) { options.fbAppId = ''; options.fbApiKey = ''; options.fbProjectId = ''; options.apiBase = ''; options.encKey = ''; options.encIV = ''; options.authURL = ''; options.userKey = options.userKey || 'v3_live_android_exozet_api_username'; options.passKey = options.passKey || 'v3_live_android_exozet_api_password'; options.appVersion = options.appVersion || '10.1.0'; options.configPrefixes = ['EUROPAPARK'].concat(options.configPrefixes || []); super(options); if (!this.config.fbApiKey) throw new Error('Missing Europa Park Firebase API Key'); if (!this.config.fbAppId) throw new Error('Missing Europa Park Firebase App ID'); if (!this.config.fbProjectId) throw new Error('Missing Europa Park Firebase Project ID'); if (!this.config.apiBase) throw new Error('Missing Europa Park API Base'); if (!this.config.encKey) throw new Error('Missing Europa Park Encryption Key'); if (!this.config.encIV) throw new Error('Missing Europa Park Encryption IV'); if (!this.config.authURL) throw new Error('Missing Europa Park Token URL'); this.cache.version = 2; this.http.injectForDomain({ hostname: new URL(this.config.authURL).hostname, }, async (method, url, data, options) => { options.headers['user-agent'] = `EuropaParkApp/${this.config.appVersion} (Android)`; }); this.http.injectForDomain({ hostname: new URL(this.config.apiBase).hostname, }, async (method, url, data, options) => { options.headers['user-agent'] = `EuropaParkApp/${this.config.appVersion} (Android)`; const jwtToken = await this.getToken(); if (jwtToken === undefined) { // refetch Firebase settings and try again await this.cache.set('auth', undefined, -1); const jwtTokenRetry = await this.getToken(); options.headers['jwtauthorization'] = `Bearer ${jwtTokenRetry}`; } else { options.headers['jwtauthorization'] = `Bearer ${jwtToken}`; options.headers['Accept-Language'] = 'en'; } }); this.http.injectForDomainResponse({ hostname: new URL(this.config.apiBase).hostname, }, async (response) => { // if error code is unauthorised, clear out our JWT token if (response.statusCode === 401) { // wipe any existing token await this.cache.set('access_token', undefined, -1); // this will be regenerated next time injectForDomain is run return undefined; } return response; }); this.bf = new Blowfish(this.config.encKey, Blowfish.MODE.CBC, Blowfish.PADDING.PKCS5); this.bf.setIv(this.config.encIV); } /** * Get or generate a Firebase device ID */ async getFirebaseID() { return await this.cache.wrap('fid', async () => { try { const fidByteArray = crypto.randomBytes(17).toJSON().data; fidByteArray[0] = 0b01110000 + (fidByteArray[0] % 0b00010000); const b64String = Buffer.from(String.fromCharCode(...fidByteArray)) .toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_'); const fid = b64String.substr(0, 22); return /^[cdef][\w-]{21}$/.test(fid) ? fid : ''; } catch (e) { this.emit('error', e); console.log(e); return ''; } }, 1000 * 60 * 60 * 24 * 8); // 8days } /** * Get Europa Park config keys */ async getConfig() { return await this.cache.wrap('auth', async () => { ``` -------------------------------- ### Get Facility Status by ID - JavaScript Source: https://themeparks.github.io/parksapi/parks_wdw_wdwdb.js Retrieves the status document for a specific facility ID. It first initializes the database, finds the facility status index, and then looks up the entity by its ID. If the entity is not found, it returns undefined. Dependencies include 'constants' and the internal 'init', 'getIndex', and 'get' methods. ```javascript async getFacilityStatus(id) { await this.init(); const statusIndex = this.getIndex(constants.INDEX_FACILITYSTATUS); // look up entity ID in our facility status index const indexEntity = statusIndex.find((x) => { return x.id === id; }); if (!indexEntity) return undefined; return await this.get(indexEntity._id); } ``` -------------------------------- ### Process and Format Park Schedules with Moment.js Source: https://themeparks.github.io/parksapi/parks_attractionsio_attractionsio.js Processes a list of day ranges to create a formatted schedule of opening and closing times for a park. It uses Moment.js with timezone information to handle date and time calculations accurately. The output is an array of schedule entries, sorted by date and opening time. ```javascript if (scheduleDescription) { scheduleEntry.description = scheduleDescription; } // Process each day range days.ranges.forEach(dayIndex => { // The dayIndex appears to be calculated as: monthIndex * 31 + dayOfMonth - 1 // where monthIndex is 0-based (0=Jan, 1=Feb, etc.) const month = Math.floor(dayIndex / 31); const day = (dayIndex % 31) + 1; // Create date object const date = moment.tz([currentYear, month, day], this.config.timezone); // Skip invalid dates if (!date.isValid() || date.date() !== day) return; // Create opening and closing times const openingTime = date.clone().hour(openHour).minute(openMinute).second(0).millisecond(0); const closingTime = date.clone().hour(closeHour).minute(closeMinute).second(0).millisecond(0); schedule.push({ date: date.format('YYYY-MM-DD'), openingTime: openingTime.format(), closingTime: closingTime.format(), ...scheduleEntry, }); }); }); // Sort schedule by date and opening time (to handle multiple venues per date) const finalSchedule = schedule.sort((a, b) => { const dateCompare = a.date.localeCompare(b.date); if (dateCompare !== 0) return dateCompare; // If dates are the same, sort by opening time return a.openingTime.localeCompare(b.openingTime); }); return [ { _id: this.config.parkId, schedule: finalSchedule, } ]; } } ``` -------------------------------- ### GET /entertainments/day_program Source: https://themeparks.github.io/parksapi/parks_plopsaland_plopsadeutschland.js Fetches the daily program for entertainment shows and events. ```APIDOC ## GET /entertainments/day_program ### Description Fetches the daily program for entertainment shows and events. ### Method GET ### Endpoint /entertainments/day_program ### Parameters #### Query Parameters - **language** (string) - Optional - The language code for the response. - **park** (string) - Optional - The park ID to filter results. ### Request Example ```json { "language": "en", "park": "plopsaland-deutschland" } ``` ### Response #### Success Response (200) - **body** (object) - Contains the entertainment program for the day. #### Response Example ```json { "body": [ { "id": "show-abc", "name": "Awesome Show", "time": "14:00", "location": "Main Stage" } ] } ``` ``` -------------------------------- ### GET /attractions/waiting-times Source: https://themeparks.github.io/parksapi/parks_plopsaland_plopsadeutschland.js Fetches the current waiting times for all attractions in the park. ```APIDOC ## GET /attractions/waiting-times ### Description Fetches the current waiting times for all attractions in the park. ### Method GET ### Endpoint /attractions/waiting-times ### Parameters #### Query Parameters - **language** (string) - Optional - The language code for the response. - **park** (string) - Optional - The park ID to filter results. ### Request Example ```json { "language": "en", "park": "plopsaland-deutschland" } ``` ### Response #### Success Response (200) - **body** (object) - Contains the waiting times for each attraction. #### Response Example ```json { "body": [ { "id": "sky-scream", "name": "Sky Scream", "waitingTime": 30 } ] } ``` ``` -------------------------------- ### Initialize LMDB Cache and Directory - JavaScript Source: https://themeparks.github.io/parksapi/cache_cacheLmdb.js This constructor initializes the LMDB cache. It ensures the cache directory exists, creating it if necessary, and then opens the LMDB database file. It relies on the 'path', 'fs', and 'lmdb' modules. The database is configured with compression enabled. ```javascript import CacheBase from './cacheBase.js'; import path from 'path'; import fs from 'fs'; import {open} from 'lmdb'; export default class CacheLmdb extends CacheBase { constructor(options = {}) { super(options); const cacheDirPath = path.join(process.cwd(), 'datacache'); if (!fs.existsSync(cacheDirPath)) { fs.mkdirSync(cacheDirPath); } const cacheDbpath = path.join(cacheDirPath, 'cache.lmdb'); this.db = open({ path: cacheDbpath, compression: true, }); } async _get(key) { return this.db.get(key); } async _set(key, object) { await this.db.put(key, object); } async _del(key) { await this.db.remove(key); } async _getKeys(prefix) { const keys = []; for (const key of this.db.getKeys()) { if (!prefix || key.startsWith(prefix)) { keys.push(key); } } return keys; } } ```