Overview
Here we are going to build simple facebook messenger chatbot using dialogflow and Node.js. we won’t be going deep into it but we will cover all kind of responses that messenger platform supports like a generic template, receipt, button, media, list and graph.
Prerequisite
- Facebook Page
- Facebook Developer Account
- Understanding of Dialogflow
- Knowledge of Node.js
Getting Started
Let’s start by creating Facebook App from Facebook Developer Account.
You will be redirected to the Dashboard, Add a Messenger Product from there
After setting up, select your Facebook page in Token Generation and Generate Token from there.
Setting Up Server
Creating a simple server in node.js is easy.
Navigate to your desired folder where you are going to setup this project. Open that folder in your Terminal and run npm init this will generate the package.json file.
Now, we have to install our dependencies,
Run npm i apiai axios body-parser express uuid –save
After installing dependencies create index.js file and import that dependencies that we just installed and create simple express server. Also, make one config.js file so we can store our credentials in that file for better code management and security purpose or instead of config.js you can also create env file.
Index.js
const apiai = require("apiai"); const express = require("express"); const bodyParser = require("body-parser"); const uuid = require("uuid"); const axios = require('axios'); //Import Config file const config = require("./config"); //setting Port app.set("port", process.env.PORT || 5000); //serve static files in the public directory app.use(express.static("public")); // Process application/x-www-form-urlencoded app.use( bodyParser.urlencoded({ extended: false }) ); // Process application/json app.use(bodyParser.json()); // Index route app.get("/", function (req, res) { res.send("Hello world, I am a chat bot"); }); // for Facebook verification app.get("/webhook/", function (req, res) { console.log("request"); if ( req.query["hub.mode"] === "subscribe" && req.query["hub.verify_token"] === config.FB_VERIFY_TOKEN ) { res.status(200).send(req.query["hub.challenge"]); } else { console.error("Failed validation. Make sure the validation tokens match."); res.sendStatus(403); } }); // Spin up the server app.listen(app.get("port"), function () { console.log("Magic Started on port", app.get("port")); });
Spin up the server. We need to make communication live so here we are using ngrok to make our localhost live but you can always use any other platform like heroku, localtunnel or any other third party services.
To make our server live type ./ngrok http 5000 in your terminal which will give you live URL. But make sure while running above command you have ngrok downloaded for Your Suitable OS. Ngrok file should be in your current working directory to successfully execute above command.
Config.js
module.exports = { FB_PAGE_TOKEN: "Page Access Token", FB_VERIFY_TOKEN: "Facebook Verification code for Webhook", API_AI_CLIENT_ACCESS_TOKEN: "DialogFlow token", FB_APP_SECRET: "Facebook Secret Code", };
FB_PAGE_TOKEN : Copy the Page Access token that we generated and paste in config.js.
FB_APP_SECRET : You will find App Secret on Settings<
Now click on set up webhook you will find that just below token generation window.
Paste your server URL with endpoint /webhook and Verify Token can be anything and check messages and messaging_postbacks.
When You click on Verify and Save You will receive verification GET request from Facebook. (make sure you copy https:// URL)
FB_VERIFY_TOKEN : Paste the verify token in your config.js file.
SERVER_URL : Copy your ngrok live URL and paste.
Dialogflow integration
Now let’s configure dialogflow with our webhook code. Add New agent and Select v1 API and copy client access token and paste in API_AI_CLIENT_ACCESS_TOKEN.
Let’s create intent in dialogflow.
- Add intent from the left sidebar.
- Give an Intent Name: Send-text
- Add Training Phrases “Hey, send me an example of a text message” or relevant to it.
- Add Action Name “send-text”
- Save it.
- Now do the same thing for send-image, send-media, send-list, send-receipt, send-Quick Reply, send-graph, send-carouselMake sure you give the unique action to all intent. We need to identify the intent of a user to send the appropriate response from our webhook server.Click on the Fulfillment tab and add your webhook endpoint here and save it.
- That’s it nothing more in Dialogflow for this example.
If you are not familiar with dialogflow please read the documentation.
Let’s come back to the index.js add this code snippet to connect with Dialogflow.
const apiAiService = apiai(config.API_AI_CLIENT_ACCESS_TOKEN, { language: "en", requestSource: "fb" }); const sessionIds = new Map();
Setup Webhook Endpoint
Now when user will send some message on facebook we will receive the post request on the our node server. So we need to handle that /webhook endpoint.
/* * All callbacks for Messenger are POST-ed. They will be sent to the same * webhook. Be sure to subscribe your app to your page to receive callbacks * for your page. * https://developers.facebook.com/docs/messenger-platform/product-overview/setup#subscribe_app * */ app.post("/webhook/", function (req, res) { var data = req.body; // Make sure this is a page subscription if (data.object == "page") { // Iterate over each entry // There may be multiple if batched data.entry.forEach(function (pageEntry) { var pageID = pageEntry.id; var timeOfEvent = pageEntry.time; // Iterate over each messaging event pageEntry.messaging.forEach(function (messagingEvent) { if (messagingEvent.message) { receivedMessage(messagingEvent); } else { console.log("Webhook received unknown messagingEvent: ",messagingEvent); } }); }); // Assume all went well. // You must send back a 200, within 20 seconds res.sendStatus(200); } });
Messages, Messaging_postbacks these two events that we checked while setting up webhook. (we are not using postback event here)
receivedMessage(messagingEvent) let’s make this function now,
function receivedMessage(event) { var senderID = event.sender.id; var recipientID = event.recipient.id; var timeOfMessage = event.timestamp; var message = event.message; if (!sessionIds.has(senderID)) { sessionIds.set(senderID, uuid.v1()); } var messageId = message.mid; var appId = message.app_id; var metadata = message.metadata; // You may get a text or attachment but not both var messageText = message.text; var messageAttachments = message.attachments; if (messageText) { //send message to api.ai sendToApiAi(senderID, messageText); } else if (messageAttachments) { handleMessageAttachments(messageAttachments, senderID); } }
If you console the event you will get JSON like,
For now, Just Focus on sender.id and What message.text.
If there is messageText in receivedMessage() function then we will call sendToApiAi().
In this function, we will first call sendTypingOn() to show that bot is typing in Messenger.
function sendToApiAi(sender, text) { sendTypingOn(sender); let apiaiRequest = apiAiService.textRequest(text, { sessionId: sessionIds.get(sender) }); apiaiRequest.on("response", response => { if (isDefined(response.result)) { handleApiAiResponse(sender, response); } }); apiaiRequest.on("error", error => console.error(error)); apiaiRequest.end(); }
Send Typing On
SendTyping will call the Facebook send API to send the Typing Action.
/* * Turn typing indicator on * */ const sendTypingOn = (recipientId) => { var messageData = { recipient: { id: recipientId }, sender_action: "typing_on" }; callSendAPI(messageData); }
callSendAPI() function will send the message data that we are generating. (here we are sending the typing on action)
/* * Call the Send API. The message data goes in the body. If successful, we'll * get the message id in a response * */ const callSendAPI = async (messageData) => { const url = "https://graph.facebook.com/v3.0/me/messages?access_token=" + config.FB_PAGE_TOKEN; await axios.post(url, messageData) .then(function (response) { if (response.status == 200) { var recipientId = response.data.recipient_id; var messageId = response.data.message_id; if (messageId) { console.log( "Successfully sent message with id %s to recipient %s", messageId, recipientId ); } else { console.log( "Successfully called Send API for recipient %s", recipientId ); } } }) .catch(function (error) { console.log(error.response.headers); }); }
Let’s come back to sendToApiAi() function next is we are calling isDefined() function to just make sure we are receiving the proper response.
const isDefined = (obj) => { if (typeof obj == "undefined") { return false; } if (!obj) { return false; } return obj != null; }
In the same function sendToApiAi() we will get the response from the Dialogflow in form of JSON.
Send that data to the handleApiAiResponse().
function handleApiAiResponse(sender, response) { let responseText = response.result.fulfillment.speech; let responseData = response.result.fulfillment.data; let messages = response.result.fulfillment.messages; let action = response.result.action; let contexts = response.result.contexts; let parameters = response.result.parameters; sendTypingOff(sender); if (responseText == "" && !isDefined(action)) { //api ai could not evaluate input. console.log("Unknown query" + response.result.resolvedQuery); sendTextMessage( sender, "I'm not sure what you want. Can you be more specific?" ); } else if (isDefined(action)) { handleApiAiAction(sender, action, responseText, contexts, parameters); } else if (isDefined(responseData) && isDefined(responseData.facebook)) { try { console.log("Response as formatted message" + responseData.facebook); sendTextMessage(sender, responseData.facebook); } catch (err) { sendTextMessage(sender, err.message); } } else if (isDefined(responseText)) { sendTextMessage(sender, responseText); } }
Send Typing Off
Remember? we started Typing on Action on Messenger now we have response to turn it off call the sendTypingOff() function.
/* * Turn typing indicator off * */ const sendTypingOff = (recipientId) => { var messageData = { recipient: { id: recipientId }, sender_action: "typing_off" }; callSendAPI(messageData); }
Send Text Message
Whenever we get unknown query from user we have to send Default message to that user.
const sendTextMessage = async (recipientId, text) => { var messageData = { recipient: { id: recipientId }, message: { text: text } }; await callSendAPI(messageData); }
Above function will call facebook send API and send the text message that we defined as default.
Now, if the Intent of user is matched with dialogflow we will get the action(action we are getting from dialogflow response) of that intent, based on the action we will send the response to each user.
When user asks : “Send me an example of text message” -> our intent “send-text” will get called and based on intent we will get the unique action of it. In my case I gave same action name as intent name.
If we get Action from dialogflow response, we are calling the handleApiAiAction().
function handleApiAiAction(sender, action, responseText, contexts, parameters) { switch (action) { case "send-text": var responseText = "This is example of Text message." sendTextMessage(sender, responseText); break; default: //unhandled action, just send back the text sendTextMessage(sender, responseText); } }
Conclusion : –
This is how you can interact with users by sending simple text message from the webhook server. Next time we will look at the rich messages like images, videos, quick-reply and receipt templates.
Comment your ideas about chatbots, we will try to build together.