diff --git a/bot.js b/bot.js new file mode 100644 index 00000000..d6587065 --- /dev/null +++ b/bot.js @@ -0,0 +1,92 @@ +'use strict'; + +// Weather Example +// See https://wit.ai/sungkim/weather/stories and https://wit.ai/docs/quickstart +const Wit = require('node-wit').Wit; +const FB = require('./facebook.js'); +const Config = require('./const.js'); + +const firstEntityValue = (entities, entity) => { + const val = entities && entities[entity] && + Array.isArray(entities[entity]) && + entities[entity].length > 0 && + entities[entity][0].value; + if (!val) { + return null; + } + return typeof val === 'object' ? val.value : val; +}; + +// Bot actions +const actions = { + say(sessionId, context, message, cb) { + console.log(message); + //console.log(sendGenericMessage(1234)); //test GenericMessage + // Bot testing mode, run cb() and return + if (require.main === module) { + cb(); + return; + } + + // Our bot has something to say! + // Let's retrieve the Facebook user whose session belongs to from context + // TODO: need to get Facebook user name + const recipientId = context._fbid_; + if (recipientId) { + // Yay, we found our recipient! + // Let's forward our bot response to her. + FB.fbMessage(recipientId, message, (err, data) => { + if (err) { + console.log( + 'Oops! An error occurred while forwarding the response to', + recipientId, + ':', + err + ); + } + // Let's give the wheel back to our bot + cb(); + }); + } else { + console.log('Oops! Couldn\'t find user in context:', context); + // Giving the wheel back to our bot + cb(); + } + }, + merge(sessionId, context, entities, message, cb) { + // Retrieve the location entity and store it into a context field + const loc = firstEntityValue(entities, 'location'); + if (loc) { + context.loc = loc; // store it in context + } + + cb(context); + }, + + error(sessionId, context, error) { + console.log(error.message); + }, + + // fetch-weather bot executes + ['fetch-weather'](sessionId, context, cb) { + // Here should go the api call, e.g.: + // context.forecast = apiCall(context.loc) + context.forecast = 'sunny'; + cb(context); + }, +}; + +const getWit = () => { + return new Wit(Config.WIT_TOKEN, actions); +}; + +exports.getWit = getWit; + +// bot testing mode +// http://stackoverflow.com/questions/6398196 +if (require.main === module) { + console.log("Bot testing mode."); + + const client = getWit(); + client.interactive(); +} \ No newline at end of file diff --git a/const.js b/const.js new file mode 100644 index 00000000..c1c65e67 --- /dev/null +++ b/const.js @@ -0,0 +1,22 @@ +'use strict'; + +// Wit.ai parameters +//const WIT_TOKEN = process.env.WIT_TOKEN; +const WIT_TOKEN = "YVTD4SSYSXSSYNHGY3TZOG6PTQMP7UWF"; +if (!WIT_TOKEN) { + throw new Error('missing WIT_TOKEN'); +} + +// Messenger API parameters +const FB_PAGE_TOKEN = process.env.FB_PAGE_TOKEN; + +var FB_VERIFY_TOKEN = process.env.FB_VERIFY_TOKEN; +if (!FB_VERIFY_TOKEN) { + FB_VERIFY_TOKEN = "just_do_it"; +} + +module.exports = { + WIT_TOKEN: WIT_TOKEN, + FB_PAGE_TOKEN: FB_PAGE_TOKEN, + FB_VERIFY_TOKEN: FB_VERIFY_TOKEN, +}; diff --git a/facebook.js b/facebook.js new file mode 100644 index 00000000..ec14b52d --- /dev/null +++ b/facebook.js @@ -0,0 +1,63 @@ +'use strict'; + +// See the Send API reference +// https://developers.facebook.com/docs/messenger-platform/send-api-reference +const request = require('request'); +const Config = require('./const.js'); + +const fbReq = request.defaults({ + uri: 'https://graph.facebook.com/me/messages', + method: 'POST', + json: true, + qs: { + access_token: Config.FB_PAGE_TOKEN + }, + headers: { + 'Content-Type': 'application/json' + }, +}); + + +const fbMessage = (recipientId, msg, cb) => { + const opts = { + form: { + recipient: { + id: recipientId, + }, + message: { + text: msg, + }, + }, + }; + + fbReq(opts, (err, resp, data) => { + if (cb) { + cb(err || data.error && data.error.message, data); + } + }); +}; + + +// See the Webhook reference +// https://developers.facebook.com/docs/messenger-platform/webhook-reference +const getFirstMessagingEntry = (body) => { + const val = body.object === 'page' && + body.entry && + Array.isArray(body.entry) && + body.entry.length > 0 && + body.entry[0] && + body.entry[0].messaging && + Array.isArray(body.entry[0].messaging) && + body.entry[0].messaging.length > 0 && + body.entry[0].messaging[0]; + + return val || null; +}; + + + +module.exports = { + getFirstMessagingEntry: getFirstMessagingEntry, + fbMessage: fbMessage, + fbReq: fbReq +}; \ No newline at end of file diff --git a/index.js b/index.js index b78f342b..b58cd788 100644 --- a/index.js +++ b/index.js @@ -1,129 +1,263 @@ -'use strict' - -const express = require('express') -const bodyParser = require('body-parser') -const request = require('request') -const app = express() - -app.set('port', (process.env.PORT || 5000)) - -// parse application/x-www-form-urlencoded -app.use(bodyParser.urlencoded({extended: false})) - -// parse application/json -app.use(bodyParser.json()) - -// index -app.get('/', function (req, res) { - res.send('hello world i am a secret bot') -}) - -// for facebook verification -app.get('/webhook/', function (req, res) { - if (req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') { - res.send(req.query['hub.challenge']) - } - res.send('Error, wrong token') -}) - -// to post data -app.post('/webhook/', function (req, res) { - let messaging_events = req.body.entry[0].messaging - for (let i = 0; i < messaging_events.length; i++) { - let event = req.body.entry[0].messaging[i] - let sender = event.sender.id - if (event.message && event.message.text) { - let text = event.message.text - if (text === 'Generic') { - sendGenericMessage(sender) - continue - } - sendTextMessage(sender, "Text received, echo: " + text.substring(0, 200)) - } - if (event.postback) { - let text = JSON.stringify(event.postback) - sendTextMessage(sender, "Postback received: "+text.substring(0, 200), token) - continue - } - } - res.sendStatus(200) -}) - - -// recommended to inject access tokens as environmental variables, e.g. -// const token = process.env.PAGE_ACCESS_TOKEN -const token = "" - -function sendTextMessage(sender, text) { - let messageData = { text:text } - - request({ - url: 'https://graph.facebook.com/v2.6/me/messages', - qs: {access_token:token}, - method: 'POST', - json: { - recipient: {id:sender}, - message: messageData, - } - }, function(error, response, body) { - if (error) { - console.log('Error sending messages: ', error) - } else if (response.body.error) { - console.log('Error: ', response.body.error) - } - }) -} - -function sendGenericMessage(sender) { - let messageData = { - "attachment": { - "type": "template", - "payload": { - "template_type": "generic", - "elements": [{ - "title": "First card", - "subtitle": "Element #1 of an hscroll", - "image_url": "http://messengerdemo.parseapp.com/img/rift.png", - "buttons": [{ - "type": "web_url", - "url": "https://www.messenger.com", - "title": "web url" - }, { - "type": "postback", - "title": "Postback", - "payload": "Payload for first element in a generic bubble", - }], - }, { - "title": "Second card", - "subtitle": "Element #2 of an hscroll", - "image_url": "http://messengerdemo.parseapp.com/img/gearvr.png", - "buttons": [{ - "type": "postback", - "title": "Postback", - "payload": "Payload for second element in a generic bubble", - }], - }] - } - } - } - request({ - url: 'https://graph.facebook.com/v2.6/me/messages', - qs: {access_token:token}, - method: 'POST', - json: { - recipient: {id:sender}, - message: messageData, - } - }, function(error, response, body) { - if (error) { - console.log('Error sending messages: ', error) - } else if (response.body.error) { - console.log('Error: ', response.body.error) - } - }) -} - -// spin spin sugar -app.listen(app.get('port'), function() { - console.log('running on port', app.get('port')) -}) +'use strict' + +const express = require('express') +const bodyParser = require('body-parser') +const request = require('request') +const app = express() +//const bot = require('node-wit').Wit +const bot = require('./bot.js') +const FB = require('./facebook.js') +// Setting up our bot +const wit = bot.getWit() + +app.set('port', (process.env.PORT || 5000)) + +// parse application/x-www-form-urlencoded +app.use(bodyParser.urlencoded({extended: false})) + +// parse application/json +app.use(bodyParser.json()) + +// index +app.get('/', function (req, res) { + res.send('hello world i am a secret bot') +}) + +// for facebook verification +app.get('/webcheck/', function (req, res) { +const messaging = FB.getFirstMessagingEntry(req.body); + if (messaging && messaging.message) { + // Yay! We got a new message! + // We retrieve the Facebook user ID of the sender + const sender = messaging.sender.id; + console.log(messaging+sender); + // We retrieve the user's current session, or create one if it doesn't exist + // This is needed for our bot to figure out the conversation history + const sessionId = findOrCreateSession(sender); + // We retrieve the message content + const msg = messaging.message.text; + const atts = messaging.message.attachments; + if (atts) { + // We received an attachment + // Let's reply with an automatic message + FB.fbMessage( + sender, + 'Sorry I can only process text messages for now.' + ); + } else if (msg) { + // We received a text message + + // Let's forward the message to the Wit.ai Bot Engine + // This will run all actions until our bot has nothing left to do + wit.runActions( + sessionId, // the user's current session + msg, // the user's message + sessions[sessionId].context, // the user's current session state + (error, context) => { + if (error) { + console.log('Oops! Got an error from Wit:', error); + } else { + // Our bot did everything it has to do. + // Now it's waiting for further messages to proceed. + console.log('Waiting for futher messages.'); + // Based on the session state, you might want to reset the session. + // This depends heavily on the business logic of your bot. + // Example: + // if (context['done']) { + // delete sessions[sessionId]; + // } + // Updating the user's current session state + sessions[sessionId].context = context; + sendTextMessage(sender, "Text received, echo: " + text.substring(0, 200)); + } + } + ); + } + } + res.sendStatus(200); +}) +// for facebook verification +app.get('/webhook/', function (req, res) { + if (req.query['hub.verify_token'] === 'my_voice_is_my_password_verify_me') { + res.send(req.query['hub.challenge']) + } + + res.send('Error, wrong token') +}) + +// to post data +app.post('/webhook/', function (req, res) { + let messaging_events = req.body.entry[0].messaging + for (let i = 0; i < messaging_events.length; i++) { + let event = req.body.entry[0].messaging[i] + let sender = event.sender.id + if (event.message && event.message.text) { + let text = event.message.text + if (text === 'Generic') { + sendGenericMessage(sender) + continue + } + //getwit(event.message) + sendTextMessage(sender, "Text received, echo: " + text.substring(0, 200)) + } + if (event.postback) { + let text = JSON.stringify(event.postback) + sendTextMessage(sender, "Postback received: "+text.substring(0, 200), token) + continue + } + } + res.sendStatus(200) +}) + + +// recommended to inject access tokens as environmental variables, e.g. +// const token = process.env.PAGE_ACCESS_TOKEN +const token = "EAAR8dpi5Ae4BANlcMZB1rK2zgS0pUDwDVZCgXA64T389NQl2ycT8KZBniXGgebFBq9N3honekW6kIzbWix4NX1pWLDeykpaDcs7AUYI6B4ZBWJkFfg83lFpmIXhBADWXhatEq9ZAXT61dnM2J7YVmvT4efglZCqFXOS5zT9ctpSgZDZD" + +function sendTextMessage(sender, text) { + let messageData = { text:text } + + request({ + url: 'https://graph.facebook.com/v2.6/me/messages', + qs: {access_token:token}, + method: 'POST', + json: { + recipient: {id:sender}, + message: messageData, + } + }, function(error, response, body) { + if (error) { + console.log('Error sending messages: ', error) + } else if (response.body.error) { + console.log('Error: ', response.body.error) + } + }) +} + +function sendGenericMessage(sender) { + let messageData = { + "attachment": { + "type": "template", + "payload": { + "template_type": "generic", + "elements": [{ + "title": "First card", + "subtitle": "Element #1 of an hscroll", + "image_url": "http://messengerdemo.parseapp.com/img/rift.png", + "buttons": [{ + "type": "web_url", + "url": "https://www.messenger.com", + "title": "web url" + }, { + "type": "postback", + "title": "Postback", + "payload": "Payload for first element in a generic bubble", + }], + }, { + "title": "Second card", + "subtitle": "Element #2 of an hscroll", + "image_url": "http://messengerdemo.parseapp.com/img/gearvr.png", + "buttons": [{ + "type": "postback", + "title": "Postback", + "payload": "Payload for second element in a generic bubble", + }], + }] + } + } + } + request({ + url: 'https://graph.facebook.com/v2.6/me/messages', + qs: {access_token:token}, + method: 'POST', + json: { + recipient: {id:sender}, + message: messageData, + } + }, function(error, response, body) { + if (error) { + console.log('Error sending messages: ', error) + } else if (response.body.error) { + console.log('Error: ', response.body.error) + } + }) +} +function getwit(messaging) +{ +//const messaging = FB.getFirstMessagingEntry(req.body); + if (messaging && messaging.message) { + // Yay! We got a new message! + // We retrieve the Facebook user ID of the sender + const sender = messaging.sender.id; + console.log(messaging+sender); + // We retrieve the user's current session, or create one if it doesn't exist + // This is needed for our bot to figure out the conversation history + const sessionId = findOrCreateSession(sender); + // We retrieve the message content + const msg = messaging.message.text; + const atts = messaging.message.attachments; + if (atts) { + // We received an attachment + // Let's reply with an automatic message + FB.fbMessage( + sender, + 'Sorry I can only process text messages for now.' + ); + } else if (msg) { + // We received a text message + + // Let's forward the message to the Wit.ai Bot Engine + // This will run all actions until our bot has nothing left to do + wit.runActions( + sessionId, // the user's current session + msg, // the user's message + sessions[sessionId].context, // the user's current session state + (error, context) => { + if (error) { + console.log('Oops! Got an error from Wit:', error); + } else { + // Our bot did everything it has to do. + // Now it's waiting for further messages to proceed. + console.log('Waiting for futher messages.'); + // Based on the session state, you might want to reset the session. + // This depends heavily on the business logic of your bot. + // Example: + // if (context['done']) { + // delete sessions[sessionId]; + // } + // Updating the user's current session state + sessions[sessionId].context = context; + sendTextMessage(sender, "Text received, echo: " + text.substring(0, 200)); + console.log(text.substring(0, 200)); + } + } + ); + } + } +} +const findOrCreateSession = (fbid) => { + let sessionId; + // Let's see if we already have a session for the user fbid + Object.keys(sessions).forEach(k => { + if (sessions[k].fbid === fbid) { + // Yep, got it! + sessionId = k; + } + }); + if (!sessionId) { + // No session found for user fbid, let's create a new one + sessionId = new Date().toISOString(); + sessions[sessionId] = { + fbid: fbid, + context: { + _fbid_: fbid + } + }; // set context, _fid_ + } + return sessionId; +}; + +// spin spin sugar +app.listen(app.get('port'), function() { + console.log('running on port', app.get('port')) +}) diff --git a/package.json b/package.json index a65dbdf8..1b42b510 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,37 @@ { - "name": "secretbots", + "name": "fagbot2", "version": "1.0.0", - "description": "", + "description": "Simple Wit-Facebook", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start": "node index.js", + "bot": "node bot.js", + "test": "jest" }, "author": "", - "license": "ISC", + "license": "MIT", + "jest": { + "scriptPreprocessor": "/node_modules/babel-jest", + "unmockedModulePathPatterns": ["core-js/.*", "sshpk/.*"] + }, "dependencies": { "body-parser": "^1.15.0", "express": "^4.13.4", - "request": "^2.71.0" - } + "node-wit": "^3.2.2", + "node-uuid": "^1.4.7", + "request": "^2.72.0", + "babel-core": "^6.0.0", + "babel-jest": "^6.0.1", + "babel-polyfill": "^6.1.4", + "bl": "^1.0.0", + "jest-cli": "^0.7.1" + }, + "repository": { + "type": "git", + "url": "https://github.com/hunkim/Wit-Facebook.git" + }, + "bugs": { + "url": "https://github.com/hunkim/Wit-Facebook/issues" + }, + "homepage": "https://github.com/hunkim/Wit-Facebook#readme" }