UNPKG

facebook-events-by-location-core

Version:
425 lines (371 loc) 17.5 kB
"use strict"; var rp = require("axios"); var EventSearch = function (options) { var self = this; self.allowedSorts = ["time", "distance", "venueDistance", "eventDistance", "venue", "popularity"]; self.allowedCategories = ["ARTS_ENTERTAINMENT", "EDUCATION", "FITNESS_RECREATION", "FOOD_BEVERAGE", "HOTEL_LODGING", "MEDICAL_HEALTH", "SHOPPING_RETAIL", "TRAVEL_TRANSPORTATION"]; self.accessToken = (process.env.FEBL_ACCESS_TOKEN && process.env.FEBL_ACCESS_TOKEN !== "" ? process.env.FEBL_ACCESS_TOKEN : null); }; EventSearch.prototype.calculateStarttimeDifference = function (currentTime, dataString) { return (new Date(dataString).getTime()-(currentTime*1000))/1000; }; EventSearch.prototype.compareVenue = function (a,b) { if (a.venue.name < b.venue.name) return -1; if (a.venue.name > b.venue.name) return 1; return 0; }; EventSearch.prototype.compareTimeFromNow = function (a,b) { if (a.timeFromNow < b.timeFromNow) return -1; if (a.timeFromNow > b.timeFromNow) return 1; return 0; }; EventSearch.prototype.compareVenueDistance = function (a,b) { var aEventDistInt = parseInt(a.distances.venue, 10); var bEventDistInt = parseInt(b.distances.venue, 10); if (aEventDistInt < bEventDistInt) return -1; if (aEventDistInt > bEventDistInt) return 1; return 0; }; EventSearch.prototype.compareEventDistance = function (a,b) { var aEventDistInt = parseInt(a.distances.event, 10); var bEventDistInt = parseInt(b.distances.event, 10); if (aEventDistInt < bEventDistInt) return -1; if (aEventDistInt > bEventDistInt) return 1; return 0; }; EventSearch.prototype.comparePopularity = function (a,b) { if ((a.stats.attending + (a.stats.maybe / 2)) < (b.stats.attending + (b.stats.maybe / 2))) return 1; if ((a.stats.attending + (a.stats.maybe / 2)) > (b.stats.attending + (b.stats.maybe / 2))) return -1; return 0; }; EventSearch.prototype.haversineDistance = function (coords1, coords2, isMiles) { //coordinate is [latitude, longitude] function toRad(x) { return x * Math.PI / 180; } var lon1 = coords1[1]; var lat1 = coords1[0]; var lon2 = coords2[1]; var lat2 = coords2[0]; var R = 6371; // km var x1 = lat2 - lat1; var dLat = toRad(x1); var x2 = lon2 - lon1; var dLon = toRad(x2); var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); var d = R * c; if(isMiles) d /= 1.60934; return d; }; EventSearch.prototype.search = function (options) { var self = this, queryOptions = {}; queryOptions.limit = options.limit || 100; queryOptions.latitude = options.lat || null; queryOptions.longitude = options.lng || null; queryOptions.distance = options.distance || 100; queryOptions.accessToken = options.accessToken ? options.accessToken : self.accessToken; queryOptions.query = options.query ? encodeURIComponent(options.query) : ""; queryOptions.categories = options.categories ? validateCategories(options.categories) : []; queryOptions.sort = options.sort || null; queryOptions.version = options.version ? options.version : "v2.10"; queryOptions.since = options.since || (new Date().getTime()/1000).toFixed(); queryOptions.until = options.until || null; queryOptions.showActiveOnly = options.showActiveOnly || true; function validateCategories (categories) { var validCategories = []; if (Array.isArray(categories) && categories.length > 0) { categories.forEach(function (category) { if (self.allowedCategories.indexOf(category.toUpperCase()) > -1) { validCategories.push(category); } }); } return validCategories; } function getPlaces(url) { var results; var temp; function getPlacesPaging(first, url, after) { var tempurl; if (after !== "") { tempurl = url + "&after=" + after; } else { tempurl = url; } return rp.get(tempurl).then(function success(responseBody) { if (responseBody.data && responseBody.data.data) { responseBody.data = responseBody.data.data } else { responseBody.data = []; } temp = responseBody; if (first) { results = temp; } else { temp.data.forEach(function (item) { results.data.push(item); }) } if (temp.paging && temp.paging.cursors && temp.paging.cursors.after) { return getPlacesPaging(false, url, temp.paging.cursors.after); } else { return results; } }) } return getPlacesPaging(true, url, ""); } function createEventObject(venue, event, currentTimestamp) { var eventResultObj = {}; var categoryList = null; if (venue.category_list && Array.isArray(venue.category_list)) { categoryList = []; venue.category_list.forEach(function (categoryObj) { if (categoryObj.name) { categoryList.push(categoryObj.name); } }); } var venueDistance = (venue.location && venue.location.latitude && venue.location.longitude ? parseInt((self.haversineDistance([venue.location.latitude, venue.location.longitude], [queryOptions.latitude, queryOptions.longitude], false)*1000).toFixed()) : null); eventResultObj.id = event.id; eventResultObj.name = event.name; eventResultObj.type = event.type; eventResultObj.coverPicture = (event.cover ? event.cover.source : null); eventResultObj.profilePicture = (event.picture ? event.picture.data.url : null); eventResultObj.description = (event.description ? event.description : null); eventResultObj.startTime = (event.start_time ? event.start_time : null); eventResultObj.endTime = (event.end_time ? event.end_time : null); eventResultObj.timezone = (event.timezone ? event.timezone : null); eventResultObj.updatedTime = (event.updated_time ? event.updated_time : null); eventResultObj.timeFromNow = self.calculateStarttimeDifference(currentTimestamp, event.start_time); eventResultObj.isCancelled = event.is_canceled; eventResultObj.category = (event.category ? event.category : null); if (event.ticketing_terms_uri || event.ticketing_privacy_uri || event.ticket_uri) { eventResultObj.ticketing = {}; if (event.ticket_uri) eventResultObj.ticketing.ticket_uri = event.ticket_uri; if (event.ticketing_terms_uri) eventResultObj.ticketing.terms_uri = event.ticketing_terms_uri; if (event.ticketing_privacy_uri) eventResultObj.ticketing.privacy_uri = event.ticketing_privacy_uri; } eventResultObj.place = {}; if (event.place && event.place.id && event.place.id !== venue.id) { if (event.place.location && Object.getOwnPropertyNames(event.place.location).length > 0) { eventResultObj.place.id = event.place.id; eventResultObj.place.name = event.place.name || null; eventResultObj.place.location = event.place.location; } else { eventResultObj.place.id = venue.id; eventResultObj.place.name = venue.name; eventResultObj.place.location = (venue.location ? venue.location : null); } } else { eventResultObj.place.id = venue.id; eventResultObj.place.name = venue.name; eventResultObj.place.location = (venue.location ? venue.location : null); } eventResultObj.distances = { venue: venueDistance, event: (eventResultObj.place.location && eventResultObj.place.location.latitude && eventResultObj.place.location.longitude ? parseInt((self.haversineDistance([eventResultObj.place.location.latitude, eventResultObj.place.location.longitude], [queryOptions.latitude, queryOptions.longitude], false)*1000).toFixed()) : null) }; eventResultObj.venue = {}; eventResultObj.venue.id = venue.id; eventResultObj.venue.name = venue.name; eventResultObj.venue.about = (venue.about ? venue.about : null); eventResultObj.venue.emails = (venue.emails ? venue.emails : null); eventResultObj.venue.coverPicture = (venue.cover ? venue.cover.source : null); eventResultObj.venue.profilePicture = (venue.picture ? venue.picture.data.url : null); eventResultObj.venue.category = (venue.category ? venue.category : null); eventResultObj.venue.categoryList = categoryList; eventResultObj.venue.location = (venue.location ? venue.location : null); return eventResultObj; } return new Promise(function (resolve, reject) { var error = null; if (!queryOptions.latitude || !queryOptions.longitude) { error = { "message": "Please specify the lat and lng parameters!", "code": 1 }; console.error(JSON.stringify(error)); reject(error); } else if (!queryOptions.accessToken) { error = { "message": "Please specify an Access Token, either as environment variable or as accessToken parameter!", "code": 2 }; console.error(JSON.stringify(error)); reject(error); } else if (queryOptions.sort && self.allowedSorts.indexOf(queryOptions.sort) === -1) { error = { "message": "An invalid sort option was passed: '" + queryOptions.sort + "'", "code": 3 }; console.error(JSON.stringify(error)); reject(error); } else { var idLimit = 50, //FB only allows 50 ids per /?ids= call currentTimestamp = (new Date().getTime()/1000).toFixed(), venuesCount = 0, venuesWithEvents = 0, eventsCount = 0, placeUrl = "https://graph.facebook.com/" + queryOptions.version + "/search" + "?type=place" + "&q=" + queryOptions.query + "&center=" + queryOptions.latitude + "," + queryOptions.longitude + "&distance=" + queryOptions.distance + "&limit=" + queryOptions.limit + "&fields=id" + "&access_token=" + queryOptions.accessToken; // Add categories if valid categories are provided if (queryOptions.categories.length > 0) { placeUrl += "&categories=" + JSON.stringify(queryOptions.categories); } //Get places as specified getPlaces(placeUrl).then(function(responseBody) { var ids = [], tempArray = [], data = responseBody.data; //Set venueCount venuesCount = data.length; //Create array of 50 places each data.forEach(function(idObj, index, arr) { tempArray.push(idObj.id); if (tempArray.length >= idLimit) { ids.push(tempArray); tempArray = []; } }); // Push the remaining places if (tempArray.length > 0) { ids.push(tempArray); } return ids; }).then(function(ids) { var urls = []; //Create a Graph API request array (promisified) ids.forEach(function(idArray, index, arr) { var eventsFields = [ "id", "type", "name", "cover.fields(id,source)", "picture.type(large)", "description", "start_time", "end_time", "category", "ticket_uri", "ticketing_privacy_uri", "ticketing_terms_uri", "place.fields(id,name,location{city,country,latitude,longitude,state,street,zip})", "is_canceled", "timezone", "updated_time" ]; var fields = [ "id", "name", "about", "emails", "cover.fields(id,source)", "picture.type(large)", "category", "category_list.fields(name)", "location{city,country,latitude,longitude,state,street,zip}", "events.fields(" + eventsFields.join(",") + ")" ]; var eventsUrl = "https://graph.facebook.com/" + queryOptions.version + "/" + "?ids=" + idArray.join(",") + "&access_token=" + queryOptions.accessToken + "&fields=" + fields.join(",") + ".since(" + queryOptions.since + ")"; if (queryOptions.until) { eventsUrl += ".until(" + queryOptions.until + ")"; } urls.push(rp.get(eventsUrl)); }); return urls; }).then(function(promisifiedRequests) { //Run Graph API requests in parallel return Promise.all(promisifiedRequests) }) .then(function(results){ var events = []; //Handle results results.forEach(function(resStr, index, arr) { var resObj = resStr.data ? resStr.data : []; Object.getOwnPropertyNames(resObj).forEach(function(venueId, index, array) { var venue = resObj[venueId]; if (venue.events && venue.events.data.length > 0) { venuesWithEvents++; venue.events.data.forEach(function(event, index, array) { // Check if only "active" events (non-draft, non-cancelled) should be shown if (queryOptions.showActiveOnly) { // Check for non-cancelled events if (!event.is_canceled) { events.push(createEventObject(venue, event, currentTimestamp)); eventsCount++; } } else { // Just push it events.push(createEventObject(venue, event, currentTimestamp)); eventsCount++; } }); } }); }); //Sort if requested if (queryOptions.sort) { switch (queryOptions.sort) { case "time": events.sort(self.compareTimeFromNow); break; case "distance": // TODO: This is kept for legacy reasons, will be removed in a future release events.sort(self.compareVenueDistance); break; case "eventDistance": events.sort(self.compareEventDistance); break; case "venueDistance": events.sort(self.compareVenueDistance); break; case "venue": events.sort(self.compareVenue); break; case "popularity": events.sort(self.comparePopularity); break; default: break; } } //Produce result object resolve({events: events, metadata: {venues: venuesCount, venuesWithEvents: venuesWithEvents, events: eventsCount}}); }).catch(function (e) { var error = { "message": e.message || "There has been an unidentified error", "code": -1 }; // Print stacktrace if (e.stack) { console.error(e.stack); } reject(error); }); } }); }; module.exports = EventSearch;