prisme-flow
Version:
prisme platform flow engine
260 lines (234 loc) • 10.9 kB
JavaScript
/**
* Created by prisme.io on 09/06/2017.
*/
module.exports = function (RED) {
'use strict'
var request = require('request')
var OAuth2 = require('oauth').OAuth2
var crypto = require('crypto')
function getOAuth2 (clientid, clientsecret) {
return new OAuth2(
clientid,
clientsecret,
'https://foursquare.com/',
'oauth2/authenticate',
'oauth2/access_token',
null
)
}
function FoursquareNode (n) {
RED.nodes.createNode(this, n)
}
RED.nodes.registerType('foursquare-credentials', FoursquareNode, {
credentials: {
displayname: {type: 'text'},
clientid: {type: 'password'},
clientsecret: {type: 'password'},
accesstoken: {type: 'password'}
}
})
/**
* Foursquare query node
*/
function FoursquareQueryNode (n) {
RED.nodes.createNode(this, n)
var node = this
node.section = n.section
node.outputNumber = parseInt(n.outputnumber) || 50
node.outputAs = n.outputas || 'multiple'
node.openday = n.openday || 'today'
node.opentime = n.opentime || 'currenttime'
var nodeCredentials = RED.nodes.getCredentials(n.foursquare)
checkCredentials(node, nodeCredentials)
this.on('input', function (msg) {
var credentials = nodeCredentials && nodeCredentials.accesstoken ? nodeCredentials : msg.credentials || {}
if (!credentials.accesstoken) {
node.error(RED._('No access token available'), msg)
return
}
if (msg.location.lat && msg.location.lon) {
// data in the node settings overwrites that in the msg
if ((!node.section || (node.section === 'empty')) &&
(msg.section &&
(msg.section === 'food' || msg.section === 'drinks' ||
msg.section === 'coffee' || msg.section === 'shops' ||
msg.section === 'arts' || msg.section === 'outdoors' ||
msg.section === 'sights' || msg.section === 'all'))) {
node.section = msg.section
}
if (node.section && (node.section !== 'empty')) {
getRecommendedVenuesNearLocation(node, credentials, msg, function (msg) {
node.send(msg)
})
} else {
node.error(RED._('problem with node input: section is not defined correctly'), msg)
node.status({fill: 'red', shape: 'ring', text: 'failed'})
}
} else {
node.error(RED._('lattitude longitude not set'), msg)
node.status({fill: 'red', shape: 'ring', text: 'failed'})
}
})
}
RED.nodes.registerType('foursquare', FoursquareQueryNode)
function checkCredentials (node, credentials) {
if (credentials && credentials.clientid && credentials.clientsecret && credentials.accesstoken) {
return true
} else {
node.warn(RED._('no credentials for node'))
return false
}
}
function getRecommendedVenuesNearLocation (node, credentials, msg, callback) {
var apiUrl = 'https://api.foursquare.com/v2/venues/explore?oauth_token=' + credentials.accesstoken
if (node.section !== 'all') {
apiUrl = apiUrl + '§ion=' + node.section
}
if (node.openday === 'any') {
apiUrl = apiUrl + '&day=any'
}
if (node.opentime === 'any') {
apiUrl = apiUrl + '&time=any'
}
apiUrl = apiUrl + '&ll=' + msg.location.lat + ',' + msg.location.lon + '&v=20141016&m=foursquare'
request.get(apiUrl, function (err, httpResponse, body) {
if (err) {
node.error(err.toString())
node.status({fill: 'red', shape: 'ring', text: 'failed'})
} else {
var result = JSON.parse(body)
if (result.meta.code != 200) {
node.error(RED._('Incorrect number of messages to output or incorrect choice of how to output them', {metaCode: result.meta.code, errorDetail: result.meta.errorDetail}), msg)
node.status({fill: 'red', shape: 'ring', text: 'failed'})
} else {
if (result.response.groups[0].items.length !== 0) {
if (node.outputNumber === 1) {
var firstVenue = result.response.groups[0].items[0]
msg.payload = {}
msg.payload = firstVenue
msg.location.lat = firstVenue.venue.location.lat
msg.location.lon = firstVenue.venue.location.lng
msg.location.name = firstVenue.venue.name
msg.location.city = firstVenue.venue.location.city
msg.location.country = firstVenue.venue.location.country
msg.title = firstVenue.venue.name
callback(msg)
} else if (node.outputAs === 'single') {
// reset the location/title information as they make no sense here
msg.location = null
msg.title = null
// returning as a single msg sets msg.payload to be an array of venues found
msg.payload = collateVenuesFound(node, result.response.groups[0].items)
callback(msg)
} else if (node.outputAs === 'multiple') {
var venues = collateVenuesFound(node, result.response.groups[0].items)
var msgs = []
for (var i = 0; i < venues.length; i++) {
var clone = RED.util.cloneMessage(msg)
clone.payload = venues[i].payload
clone.location = venues[i].location
clone.title = venues[i].title
msgs[i] = clone
}
callback([msgs])
} else {
// shouldn't ever get here
node.error(RED._('Incorrect number of messages to output or incorrect choice of how to output them'), msg)
node.status({fill: 'red', shape: 'ring', text: 'failed'})
}
} else {
msg.payload = null
callback(msg)
}
}
}
})
}
function collateVenuesFound (node, venuesFound) {
var venues = []
var numberToReturn = Math.min(node.outputNumber, venuesFound.length)
for (var i = 0; i < numberToReturn; i++) {
var venue = venuesFound[i]
venues[i] = {}
venues[i].payload = venue
venues[i].location = {}
venues[i].location.lat = venue.venue.location.lat
venues[i].location.lon = venue.venue.location.lng
venues[i].location.name = venue.venue.name
venues[i].location.city = venue.venue.location.city
venues[i].location.country = venue.venue.location.country
venues[i].title = venue.venue.name
}
return venues
}
RED.httpAdmin.get('/foursquare-credentials/auth', function (req, res) {
if (!req.query.clientid || !req.query.clientsecret || !req.query.id || !req.query.callback) {
return res.status(400).send(RED._('ERROR: request does not contain the required parameters'))
}
var nodeid = req.query.id
var credentials = RED.nodes.getCredentials(nodeid) || {}
credentials.clientid = req.query.clientid || credentials.clientid
credentials.clientsecret = req.query.clientsecret || credentials.clientsecret
if (!credentials.clientid || !credentials.clientsecret) {
return res.status(400).send(RED._('ERROR: client ID and client secret are not defined'))
}
var csrfToken = crypto.randomBytes(18).toString('base64').replace(/\//g, '-').replace(/\+/g, '_')
res.cookie('csrf', csrfToken)
credentials.csrftoken = csrfToken
RED.nodes.addCredentials(nodeid, credentials)
var oa2 = getOAuth2(credentials.clientid, credentials.clientsecret)
var url = oa2.getAuthorizeUrl({redirect_uri: req.query.callback, response_type: 'code', state: nodeid + ':' + csrfToken})
res.redirect(url)
})
RED.httpAdmin.get('/foursquare-credentials/auth/callback', function (req, res) {
if (req.query.error) {
return res.send(RED._('ERROR: __queryError__: __description__', {queryError: req.query.error, description: req.query.error_description}))
}
var state = req.query.state.split(':')
var nodeid = state[0]
var credentials = RED.nodes.getCredentials(nodeid)
if (!credentials || !credentials.clientid || !credentials.clientsecret) {
return res.status(400).send(RED._('ERROR: no credentials - should never happen'))
}
if (state[1] !== credentials.csrftoken) {
return res.status(401).send(RED._('CSRF token mismatch, possible cross-site request forgery attempt.'))
}
var clientid = credentials.clientid
var clientsecret = credentials.clientsecret
var oa2 = getOAuth2(clientid, clientsecret)
var arr = req.url.split('?')
var callback = req.protocol + '://' + req.get('host') + arr[0]
oa2.getOAuthAccessToken(
req.query.code,
{redirect_uri: callback, grant_type: 'authorization_code'},
function (error, oauth_access_token, oauth_refresh_token, results) {
if (error) {
var resp = RED._('foursquare.error.oauth-error-status', {statusCode: error.statusCode, errorData: error.data})
res.send(resp)
} else {
var apiUrl = 'https://api.foursquare.com/v2/users/self?oauth_token=' + oauth_access_token + '&v=20141016'
var r = request.get(apiUrl, function (err, httpResponse, body) {
if (err) {
var resp = RED._('Something went wrong with the authentication process. The following error was returned:', {statusCode: error.statusCode, errorData: error.data})
res.send(resp)
} else {
var result = JSON.parse(body)
if (result.meta.code != 200) {
var message = RED._('Something went wrong with the authentication process. Http return code:', {metaCode: result.meta.code})
res.send(message)
} else {
credentials = {}
credentials.displayname = result.response.user.firstName + ' ' + result.response.user.lastName
credentials.clientid = clientid
credentials.clientsecret = clientsecret
credentials.accesstoken = oauth_access_token
RED.nodes.addCredentials(nodeid, credentials)
res.send(RED._('Authorised - you can close this window and return to prisme-flow'))
}
}
})
}
}
)
})
}