From 51d1f90ae14dccfe80d399ba3f789937be435fd4 Mon Sep 17 00:00:00 2001 From: Manoj L Date: Fri, 3 Apr 2020 15:07:35 +0530 Subject: [PATCH] Issue #000 feat: Whatsapp integration through gupshup.com code --- router/appWhatsapp.js | 217 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 router/appWhatsapp.js diff --git a/router/appWhatsapp.js b/router/appWhatsapp.js new file mode 100644 index 0000000..597cd13 --- /dev/null +++ b/router/appWhatsapp.js @@ -0,0 +1,217 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const cors = require('cors'); +const axios = require('axios'); +var https = require('https'); +var http = require('http'); +var fs = require('fs'); +var origins = require('./config/corsList'); +var LOG = require('./log/logger'); +var literals = require('./config/literals'); +var config = require('./config/config'); +var RasaCoreController = require('./controllers/rasaCoreController'); +var EDB = require('./api/elastic/connection'); +const appBot = express(); + +// IVR is best done outside the bot...as no NLU interpretation is required + +// Cors handling +appBot.use(cors()); + +// Body parsers for requests +appBot.use(bodyParser.json()); +appBot.use(bodyParser.urlencoded({ extended: false })); + +// This object tracks session. It needs to be moved to Redis +var memory = {}; + +// Route that receives a POST request to /bot +appBot.post('/whatsapp', function (req, res) { + LOG.info('req.body:') + LOG.info(req.body); + + // Payload set when setting up webhook URL in gupshup: i.e: Enter your Callback URL + /*{"app": "DemoAppName", "timestamp": 1585903899563, "version": 2, "type": "user-event", + "payload": {"phone": "callbackSetPhone", "type": "sandbox-start"}}*/ + + // Regular payload + /*{{"app": "DemoApp", "timestamp": 1580227766370, "version": 2, "type": "message", + "payload": { "id": "ABEGkYaYVSEEAhAL3SLAWwHKeKrt6s3FKB0c", "source": "9876543210", "type": "text", + "payload": {"text": "1"}, + "sender": {"phone": "9876543210","name": "Manoj"}}}*/ + + const payloadType = req.body.type;//: + + LOG.info("req.body.type:"); + LOG.info(payloadType); + + // If payload type is user-event, just send 1 as response, this is mostly setup webhook call + if (payloadType === "user-event") { + res.send('1'); + + return; + } + + const body = req.body.payload.payload.text; // 'I am CBSE student from Maharashtra'; + const payloadNumber = req.body.payload.sender.phone; // 9876543210 + + LOG.info("req.body.payload.payload.text:"); + LOG.info(body); + LOG.info("req.body.payload.sender.phone:"); + LOG.info(payloadNumber); + + data = { message: body, customData: { userId: md5(payloadNumber) } }; + sessionID = md5(payloadNumber); + + LOG.info('context for: ' + sessionID); + LOG.info(memory[sessionID]); + + getResponseFromRasa(sessionID, res, body); +}); + +// Calls Rasa, and sends resposne to user +function getResponseFromRasa(sessionID, res, body) { + res.set("Content-Type", "text/plain"); + + // Persisting incoming data to EDB + dataPersist = { 'message': body, 'channel': 'whatsapp' }; + // EDB.saveToEDB(dataPersist, 'user', sessionID,(err,response)=>{}) + + if (body == '0') { + memory = {} + } + // all non numeric user messages go to bot + if (isNaN(body)) { + ///Bot interaction flow + RasaCoreController.processUserData(data, sessionID, (err, resp) => { + if (err) { + emitToUser(sessionID, res, literals.message.SORRY) + } else { + let responses = resp.res; + for (var i = 0; i < responses.length; i++) { + emitToUser(sessionID, res, responses[i].text) + } + } + }) + } + // this section with numeric input does not require NLU interpretation so it will not go to Bot + else if (memory[sessionID]) { + ////this if-else cascade is difficult to maintain, so working on a json config to drive this decision tree + if (memory[sessionID]['role']) { + if (memory[sessionID]['educationLvl']) { + if (memory[sessionID]['board']) { + if (memory[sessionID]['board'] == '1') { + emitToUser(sessionID, res, literals.message.LINK + 'CBSE') + delete memory[sessionID] + } else if (memory[sessionID]['board'] == '2') { + if (memory[sessionID]['boardType']) { + if (memory[sessionID]['boardType'] == '1') { + emitToUser(sessionID, res, literals.message.LINK + 'AP') + delete memory[sessionID] + } + if (memory[sessionID]['boardType'] == '2') { + emitToUser(sessionID, res, literals.message.LINK + 'KA') + delete memory[sessionID] + } + if (memory[sessionID]['boardType'] == '3') { + emitToUser(sessionID, res, literals.message.LINK + 'TN') + delete memory[sessionID] + } + } + else { + LOG.info('setting up boardType:' + body) + memory[sessionID]['boardType'] = body + if (memory[sessionID]['boardType'] == '1') { + emitToUser(sessionID, res, literals.message.LINK + 'AP') + delete memory[sessionID] + } + if (memory[sessionID]['boardType'] == '2') { + emitToUser(sessionID, res, literals.message.LINK + 'KA') + delete memory[sessionID] + } + if (memory[sessionID]['boardType'] == '3') { + emitToUser(sessionID, res, literals.message.LINK + 'TN') + delete memory[sessionID] + } + } + } else { + emitToUser(sessionID, res, literals.message.BOARD_NOT_HANDLED) + } + } + else { + if (body == '1') { + LOG.info('setting up board:' + body) + memory[sessionID]['board'] = body + emitToUser(sessionID, res, literals.message.LINK + 'CBSE') + delete memory[sessionID] + } else if (body == '2') { + LOG.info('setting up board:' + body) + memory[sessionID]['board'] = body + emitToUser(sessionID, res, literals.message.CHOOSE_STATE) + } else { + emitToUser(sessionID, res, literals.message.BOARD_NOT_HANDLED) + + } + } + } + else { + LOG.info('setting up educationLvl:' + body) + memory[sessionID]['educationLvl'] = body + if (body == '2') { + emitToUser(sessionID, res, literals.message.LINK) + delete memory[sessionID] + } else { + emitToUser(sessionID, res, literals.message.CHOOSE_BOARD) + } + } + } + else { + LOG.info('setting up role:' + body) + memory[sessionID]['role'] = body + emitToUser(sessionID, res, literals.message.EDUCATION_LEVEL) + } + } else { + memory[sessionID] = {} + emitToUser(sessionID, res, literals.message.MENU) + } +} + +// Send data to user (gupshup in this case) +function emitToUser(sessionID, client, text) { + // Persisting outgoing data to EDB + dataPersist = { message: text, channel: "rest" }; + // EDB.saveToEDB(dataPersist, 'bot', sessionID,(err,response)=>{}) + + // Emit to client + client.send(text); +} + +// Md5 +function md5(d){return rstr2hex(binl2rstr(binl_md5(rstr2binl(d),8*d.length)))}function rstr2hex(d){for(var _,m="0123456789ABCDEF",f="",r=0;r>>4&15)+m.charAt(15&_);return f}function rstr2binl(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<>5]>>>m%32&255);return _}function binl_md5(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_} + +// Http endpoint +http.createServer(appBot).listen(config.REST_HTTP_PORT, function (err) { + if (err) { + throw err; + } + + LOG.info('Server started on port ' + config.REST_HTTP_PORT); +}); + +// Https endpoint only started if you have updated the config with key/crt files +if (config.HTTPS_PATH_KEY) { + //https certificate setup + var options = { + key: fs.readFileSync(config.HTTPS_PATH_KEY), + cert: fs.readFileSync(config.HTTPS_PATH_CERT), + ca: fs.readFileSync(config.HTTPS_PATH_CA) + }; + + https.createServer(options, appBot).listen(config.REST_HTTPS_PORT, function (err) { + if (err) { + throw err; + } + + LOG.info('Server started on port ' + config.REST_HTTPS_PORT); + }); +}