prisme-flow
Version:
prisme platform flow engine
551 lines (534 loc) • 22.7 kB
JavaScript
/**
* Created by prisme.io on 09/06/2017.
*/
module.exports = function (RED) {
'use strict'
var request = require('request')
function GooglePlacesQueryNode (n) {
RED.nodes.createNode(this, n)
this.googleAPI = RED.nodes.getNode(n.googleAPI)
var node = this
var reqType = n.reqType
var outputAs = n.outputas || 'single'
var outputNum = parseInt(n.outputnumber, 10) || 1
// Grab node properties
var nodeLanguage = n.language
var nodeLon = n.lon
var nodeLat = n.lat
var nodeRadius = n.radius
var nodeRankBy = n.rankBy
var nodeKeyword = n.keyword
var nodeMinPrice = n.minPrice
var nodeMaxPrice = n.maxPrice
var nodeName = n.placeName
var nodeOpenNow = n.openNow
var nodeTypes = n.types
var nodePlaceId = n.placeId
var nodeExtensions = n.extensions
var nodeQuery = n.query
this.on('input', function (msg) {
// setup and clear vars on new input
var url, key, lat, lon, radius, rankBy, keyword, language, minPrice, maxPrice, name, types, query, placeId, extensions, openNow
var queryParams = {}, error = {}
node.status({fill: 'blue', shape: 'dot', text: 'querying'})
if (this.googleAPI && this.googleAPI.credentials && this.googleAPI.credentials.key) {
key = this.googleAPI.credentials.key
} else {
key = msg.key
}
if (nodeLat || nodeLat === 0) {
lat = nodeLat
} else if (msg.location && msg.location.hasOwnProperty('lat')) {
lat = msg.location.lat
}
if (nodeLon || nodeLon === 0) {
lon = nodeLon
} else if (msg.location && msg.location.hasOwnProperty('lon')) {
lon = msg.location.lon
}
if (nodeRadius) {
radius = nodeRadius
} else if (msg.location && msg.location.radius) {
radius = msg.location.radius
}
rankBy = nodeRankBy || msg.rankby || 'prominence'
keyword = nodeKeyword || msg.keyword
language = nodeLanguage || msg.language
if (nodeMinPrice || nodeMinPrice === 0) {
minPrice = nodeMinPrice
} else if (msg.hasOwnProperty('minprice')) {
minPrice = msg.minprice
}
if (nodeMaxPrice || nodeMaxPrice === 0) {
maxPrice = nodeMaxPrice
} else if (msg.hasOwnProperty('maxprice')) {
maxPrice = msg.maxprice
}
name = nodeName || msg.name
types = nodeTypes || msg.types
query = nodeQuery || msg.query || msg.payload
placeId = nodePlaceId || msg.placeid
extensions = nodeExtensions || msg.extensions
openNow = nodeOpenNow || msg.opennow
// Key is required for all reqTypes
if (!key) {
console.error('API Key missing!')
error = {
code: 400,
status: 'MISSING_VALUES',
message: RED._('Please provide your application API key.')
}
throwNodeError(node, msg, error)
return
}
// Radius must always be less than 50000 if supplied
if (radius && radius > 50000) {
radius = 50000
}
if (reqType === 'placesNearby') { // Google Places Nearby request
url = 'https://maps.googleapis.com/maps/api/place/nearbysearch/json'
// Lat/Lon is required
if (!(lat || lat === 0) || !(lon || lon === 0)) {
console.error('Latitude/Longitude missing!')
error = {
code: 400,
status: 'MISSING_VALUES',
message: RED._('Please provide a longitude and latitude.')
}
throwNodeError(node, msg, error)
return
}
// Check rankby to determine which other params are required
if (rankBy && rankBy === 'distance') { // keyword/name/types required. radius not allowed.
if (radius) {
console.error('rankby set to distance, but radius still supplied.')
error = {
code: 400,
status: 'BAD_REQUEST',
message: RED._('If rankby is set to distance, then radius is not allowed in the request. Instead, you are required to provide at least one of keyword/name/types.')
}
throwNodeError(node, msg, error)
return
} else if (!(keyword || name || types)) {
console.error('rankby set to distance, and keyword/name/types not supplied.')
msg.payload = {}
error = {
code: 400,
status: 'BAD_REQUEST',
message: RED._('If rankby is set to distance, you are required to provide at least one of keyword/name/types.')
}
throwNodeError(node, msg, error)
return
}
} else if (((rankBy && rankBy === 'prominence') || (!rankBy)) && !radius) { // radius required
radius = 50000
} else if (rankBy && (rankBy !== 'prominence' && rankBy !== 'distance')) { // some other value was provided for rankBy, which is not allowed
console.error('Value other than \'prominence\' or \'distance\' provided for rankby.')
error = {
code: 400,
status: 'BAD_REQUEST',
message: RED._('The allowed options for rankby are prominence (default) and distance')
}
throwNodeError(node, msg, error)
return
}
} else if (reqType === 'placesDetails') { // Google Places Details request
url = 'https://maps.googleapis.com/maps/api/place/details/json'
// Check for placeId (required)
if (!placeId) { // Throw error if placeId (required) is missing
console.error('placeId missing!')
error = {
code: 400,
status: 'MISSING_VALUES',
message: RED._('Please provide a placeid.')
}
throwNodeError(node, msg, error)
return
}
} else { // Google Places Text request (catches if reqType is not provided for compatibility purposes)
url = 'https://maps.googleapis.com/maps/api/place/textsearch/json'
// Check for query (required)
if (!query) { // Throw error if query (required) is missing
console.error('Query missing!')
error = {
code: 400,
status: 'MISSING_VALUES',
message: RED._('Please provide a query for the places text search.')
}
throwNodeError(node, msg, error)
return
}
if ((lat || lat === 0) && (lon || lon === 0) && !radius) {
radius = 50000
}
}
if ((lat || lat === 0) && (lon || lon === 0)) {
queryParams.location = lat + ',' + lon
}
if (radius) {
queryParams.radius = radius
}
if (rankBy !== 'prominence') {
queryParams.rankby = rankBy
}
if (keyword) {
queryParams.keyword = keyword
}
if (language) {
queryParams.language = language
}
if (minPrice || minPrice === 0) {
queryParams.minprice = minPrice
}
if (maxPrice || maxPrice === 0) {
queryParams.maxprice = maxPrice
}
if (name) {
queryParams.name = name
}
if (types) {
queryParams.types = types
}
if (query) {
queryParams.query = query
}
if (placeId) {
queryParams.placeid = placeId
}
if (extensions) {
queryParams.extensions = extensions
}
if (openNow) {
queryParams.opennow = openNow
}
if (key) {
queryParams.key = key
}
// console.log("URL: " + url);
// console.log(queryParams);
// Clear payload
msg.payload = {}
// Send request to Google
request.get({
url: url,
qs: queryParams,
method: 'GET'
}, function (err, resp, body) {
if (err) {
console.error(err)
error = {
code: 500,
status: err.code,
message: err
}
throwNodeError(node, msg, error)
return
} else {
body = JSON.parse(body)
var msgResp, msgData
if (body.status !== 'OK') {
// console.error("status not ok!");
msgResp = {
'status': body.status,
'error_message': body.error_message
}
switch (body.status) {
case 'ZERO_RESULTS':
msg.statusCode = 200
msg.payload.results = []
break
case 'OVER_QUERY_LIMIT':
msg.statusCode = 429
break
case 'REQUEST_DENIED':
msg.statusCode = 401
break
case 'INVALID_REQUEST':
msg.statusCode = 400
break
default:
msg.statusCode = 500
}
msg.status = body.status
if (msg.statusCode !== 200) {
msg.error = body.error_message || ''
if (reqType === 'placesDetails' && msg.statusCode === 400) {
msg.status = 'BAD_REQUEST'
msg.error = RED._('Please provide a valid placeid.')
}
node.status({fill: 'red', shape: 'ring', text: 'failed'})
throwNodeError(node, msg, {
code: msg.statusCode,
status: msg.status,
message: msg.error
})
return
}
} else {
// console.log("status ok!");
node.status({})
var result, i, msgs
if (reqType === 'placesNearby') {
if (outputNum === 1) {
result = body.results[0]
msg.html_attributions = body.html_attributions
msg.data = body
msg.status = body.status
msg.title = result.name
msg.placeid = result.place_id
msg.description = result.name + ', ' + result.vicinity
msg.location = {
lat: result.geometry.location.lat,
lon: result.geometry.location.lng,
vicinity: result.vicinity,
description: result.name + ', ' + result.vicinity
}
msg.payload = {
'placeid': result.place_id,
'name': result.name,
'vicinity': result.vicinity,
'types': result.types
}
if (result.hasOwnProperty('rating')) {
msg.payload.rating = result.rating
}
if (result.hasOwnProperty('price_level')) {
msg.payload.pricelevel = result.price_level
}
if (result.opening_hours && result.opening_hours.hasOwnProperty('open_now')) {
msg.payload.opennow = result.opening_hours.open_now
}
} else if (outputAs === 'single') { // send one combined message
msg.html_attributions = body.html_attributions
msg.data = body
msg.status = body.status
msg.title = (Math.min(outputNum, body.results.length)) + ' ' + RED._('results returned')
msg.payload = []
for (i = 0; i < (Math.min(outputNum, body.results.length)); i++) {
result = body.results[i]
msg.payload[i] = {}
msg.payload[i].title = result.name
msg.payload[i].placeid = result.place_id
msg.payload[i].description = result.name + ', ' + result.vicinity
msg.payload[i].location = {
lat: result.geometry.location.lat,
lon: result.geometry.location.lng,
vicinity: result.vicinity,
description: result.name + ', ' + result.vicinity
}
msg.payload[i].payload = {
'placeid': result.place_id,
'name': result.name,
'vicinity': result.vicinity,
'types': result.types
}
if (result.hasOwnProperty('rating')) {
msg.payload[i].payload.rating = result.rating
}
if (result.hasOwnProperty('price_level')) {
msg.payload[i].payload.pricelevel = result.price_level
}
if (result.opening_hours && result.opening_hours.hasOwnProperty('open_now')) {
msg.payload[i].payload.opennow = result.opening_hours.open_now
}
}
} else { // send multiple messages
msgs = []
for (i = 0; i < (Math.min(outputNum, body.results.length)); i++) {
msgs[i] = {}
result = body.results[i]
msgs[i].html_attributions = body.html_attributions
msgs[i].data = body
msgs[i].status = body.status
msgs[i].title = result.name
msgs[i].placeid = result.place_id
msgs[i].description = result.name + ', ' + result.vicinity
msgs[i].location = {
lat: result.geometry.location.lat,
lon: result.geometry.location.lng,
vicinity: result.vicinity,
description: result.name + ', ' + result.vicinity
}
msgs[i].payload = {
'placeid': result.place_id,
'name': result.name,
'vicinity': result.vicinity,
'types': result.types
}
if (result.hasOwnProperty('rating')) {
msgs[i].payload.rating = result.rating
}
if (result.hasOwnProperty('price_level')) {
msgs[i].payload.pricelevel = result.price_level
}
if (result.opening_hours && result.opening_hours.hasOwnProperty('open_now')) {
msgs[i].payload.opennow = result.opening_hours.open_now
}
}
msg = [msgs]
}
} else if (reqType === 'placesDetails') {
result = body.result
msg.html_attributions = body.html_attributions
msg.data = body
msg.status = body.status
msg.title = result.name
msg.description = result.name + ', ' + result.formatted_address
msg.location = {
lat: result.geometry.location.lat,
lon: result.geometry.location.lng,
address: result.formatted_address,
description: result.name + ', ' + result.formatted_address
}
msg.payload = {
'name': result.name,
'address': result.formatted_address,
'phone': result.formatted_phone_number,
'website': result.website || body.result.url
}
if (result.hasOwnProperty('rating')) {
msg.payload.rating = body.result.rating
}
if (result.hasOwnProperty('price_level')) {
msg.payload.pricelevel = body.result.price_level
}
if (result.opening_hours && result.opening_hours.hasOwnProperty('open_now')) {
msg.payload.opennow = result.opening_hours.open_now
}
if (result.opening_hours && result.opening_hours.hasOwnProperty('open')) {
msg.payload.open = result.opening_hours.open
}
if (result.opening_hours && result.opening_hours.hasOwnProperty('close')) {
msg.payload.opennow = result.opening_hours.close
}
if (result.hasOwnProperty('permanently_closed')) {
msg.payload.permanently_closed = result.permanently_closed
}
} else { // fall into placesText for compatibility purposes
msg.status = body.status
msg.html_attributions = body.html_attributions
if (outputNum === 1) {
result = body.results[0]
msg.payload = result.name
msg.title = result.name
msg.placeid = result.place_id
if (result.formatted_address) {
msg.payload += ', ' + result.formatted_address
}
if (result.geometry && result.geometry.location &&
result.geometry.location.hasOwnProperty('lat') &&
result.geometry.location.hasOwnProperty('lng')) {
msg.location = {
lat: result.geometry.location.lat,
lon: result.geometry.location.lng,
address: result.formatted_address,
description: msg.payload
}
}
msg.description = msg.payload
msg.detailsJson = {
'placeid': result.place_id,
'name': result.name,
'address': result.formatted_address,
'types': result.types
}
if (result.hasOwnProperty('price_level')) {
msg.detailsJson.pricelevel = result.price_level
}
if (result.hasOwnProperty('rating')) {
msg.detailsJson.rating = result.rating
}
if (result.opening_hours && result.opening_hours.hasOwnProperty('open_now')) {
msg.detailsJson.opennow = result.opening_hours.open_now
}
} else if (outputAs === 'single') { // send one combined message
msg.payload = []
msg.title = (Math.min(outputNum, body.results.length)) + ' ' + RED._('places.message.results-returned')
for (i = 0; i < (Math.min(outputNum, body.results.length)); i++) {
msg.payload[i] = {}
msg.payload[i].title = body.results[i].name
msg.payload[i].payload = body.results[i].name
msg.payload[i].placeid = body.results[i].place_id
if (body.results[i].formatted_address) {
msg.payload[i].payload += ', ' + body.results[i].formatted_address
}
if (body.results[i].geometry && body.results[i].geometry.location &&
body.results[i].geometry.location.lat &&
body.results[i].geometry.location.lng) {
msg.payload[i].location = {
lat: body.results[i].geometry.location.lat,
lon: body.results[i].geometry.location.lng,
address: body.results[i].formatted_address,
description: msg.payload[i].payload
}
}
msg.payload[i].description = msg.payload[i].payload
msg.payload[i].detailsJson = {
'placeid': body.results[i].place_id,
'name': body.results[i].name,
'address': body.results[i].formatted_address,
'types': body.results[i].types
}
if (body.results[i].hasOwnProperty('price_level')) {
msg.payload[i].detailsJson.pricelevel = body.results[i].price_level
}
if (body.results[i].hasOwnProperty('rating')) {
msg.payload[i].detailsJson.rating = body.results[i].rating
}
if (body.results[i].opening_hours && body.results[i].opening_hours.hasOwnProperty('open_now')) {
msg.payload[i].detailsJson.opennow = body.results[i].opening_hours.open_now
}
}
} else { // send multiple messages
msgs = []
for (i = 0; i < (Math.min(outputNum, body.results.length)); i++) {
msgs[i] = {}
msgs[i].title = body.results[i].name
msgs[i].placeid = body.results[i].place_id
msgs[i].payload = body.results[i].name
if (body.results[i].formatted_address) {
msgs[i].payload += ', ' + body.results[i].formatted_address
}
if (body.results[i].geometry && body.results[i].geometry.location &&
body.results[i].geometry.location.lat &&
body.results[i].geometry.location.lng) {
msgs[i].location = {
lat: body.results[i].geometry.location.lat,
lon: body.results[i].geometry.location.lng,
address: body.results[i].formatted_address,
description: msgs[i].payload
}
}
msgs[i].description = msgs[i].payload
msgs[i].detailsJson = {
'placeid': body.results[i].place_id,
'name': body.results[i].name,
'address': body.results[i].formatted_address,
'types': body.results[i].types
}
if (body.results[i].hasOwnProperty('price_level')) {
msgs[i].detailsJson.pricelevel = body.results[i].price_level
}
if (body.results[i].hasOwnProperty('rating')) {
msgs[i].detailsJson.rating = body.results[i].rating
}
if (body.results[i].opening_hours && body.results[i].opening_hours.hasOwnProperty('open_now')) {
msgs[i].detailsJson.opennow = body.results[i].opening_hours.open_now
}
}
msg = [msgs]
}
}
}
}
node.send(msg)
})
})
}
RED.nodes.registerType('google places', GooglePlacesQueryNode)
function throwNodeError (node, msg, error) {
msg.error = error
node.status({fill: 'red', shape: 'ring', text: 'failed'})
node.error(error, msg)
}
}