Tutorial: Facebook ChatBot

Facebook ChatBot
Inspecting The Code

In previous tutorials we dealt with routing requests through the use of routes and controllers so we won’t cover that here.  In face very little changes in the main server.js file from previous tutorials as most of the mechanics of handling the incoming Facebook request is done completely inside the controller so we will take a look at that now.

/** Export our route map */
module.exports.route = function(app) {

  /** This endpoint is called by Facebook when enabling the application and a token
      verification is required */
  app.get('/webhook', function(req, res, next) {
    if (req.query.hub.mode === 'subscribe' &&
      req.query.hub.verify_token === fbConfig.verifyToken) {
      log.info("Validating webhook");
      res.send(200,parseInt(req.query.hub.challenge));
    } else {
      log.error("Failed validation. Make sure the validation tokens match.");
      res.send(403);
    }
  });


  /** This endpoint is called when an incoming message is received by Facebook */
  app.post('/webhook', function(req, res, next) {
    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(entry) {
        var pageID = entry.id;
        var timeOfEvent = entry.time;
        // Iterate over each messaging event
        entry.messaging.forEach(function(event) {
          if (event.message) {
            receivedMessage(event);
          } else if (event.postback) {
            receivedPostback(event);
          } else {
            console.log("Webhook received unknown event: ", event);
          }
        });
      });

      // Assume all went well.
      //
      // You must send back a 200, within 20 seconds, to let us know
      // you've successfully received the callback. Otherwise, the request
      // will time out and we will keep trying to resend.
      res.send(200);
    }
  });
}

Lines 410 through 455 of controllers/webhook.js show the incoming route mappings and handlers for the two Facebook request types.

When first verifying the user-specified token Facebook will make a GET request to the application with the token and a challenge as payload parameters.  It requires the application to verify the token, as we do on line 417 and to then respond with the challenge as is seen on line 419.

When a Facebook user posts a message to our page Facebook triggers the webhook we registered with it using a POST request which is handled on line 428.  Because multiple requests may come in at once during a single call we implement logic between lines 433 and 445 to loop through each ‘event’ and process it accordingly.

Between lines 438 and 442 we deal with the two message types one being an incoming text message and the other being a postback which we will see how to generate and handle down below.

// This function deconstructs the incoming event off the wire and determines
// the message type.  Text messages are passed through the 'classifier' which
// uses natural language algorithms to determine a specific keyword for the
// ChatBot to act upon.
function receivedMessage(event) {
  var senderID = event.sender.id;
  var recipientID = event.recipient.id;
  var timeOfMessage = event.timestamp;
  var message = event.message;

  console.log("Received message for user %d and page %d at %d with message:",
    senderID, recipientID, timeOfMessage);
  console.log(JSON.stringify(message));

  var messageId = message.mid;
  var messageText = message.text;
  var messageAttachments = message.attachments;
  var messagePostback = message.payload;
  if (messageText) {

    // If we receive a text message, check to see if it matches a keyword
    // and send back the example. Otherwise, just echo the text we received.
    switch (classifier.classify(messageText)) {
      // Be friendly!  After all civiility costs nothing except a few
      // lines of code :p
      case 'greet':
        sendTextMessage(senderID, 'Hi! How can I help you today?  You can ask me things like "show me the categories" or "what\'s the weather in Johannesburg" and I\'ll try me best to help.  So, what will it be?');
         break;

      // Lists the categories
      case 'categories':
        sendTextMessage(senderID, 'Okay, let me retrieve them for you.');
        sendCategoriesMessage(senderID);
        break;

      // Weather related searches
      case 'weather':
        sendWeatherMessage(senderID, messageText);
        break;

      default:
        sendTextMessage(senderID, 'I\'m sorry but I did not understand your question.  You can ask me things like "show me the categories" or "what\'s the weather in Johannesburg" and I\'ll try me best to help.  So, what will it be?');
    }
  }  else if (messageAttachments) {
    sendTextMessage(senderID, "Message with attachment received");
  }
}

Lines 341 through 387 depict the handling of text messages.  Whilst most of the function relates to obtaining the required messaging parameters it is on line 363 that we pass the incoming text through our natural language classifier.  Depending on the strength of the classification we branch our to different parts of the code.

The classifier’s basic training code is located between lines 60 and 83 of controllers/webhook.js

// Natural Language Module
// Here we train our module to match certain strings and phrases
// to keywords
var classifier = new natural.BayesClassifier();
// Greetings
classifier.addDocument('hi', 'greet');
classifier.addDocument('hello', 'greet');
classifier.addDocument('g\'day', 'greet');
classifier.addDocument('how are you', 'greet');

// Categories
classifier.addDocument('list catergories', 'categories');
classifier.addDocument('show me the categories', 'categories');
classifier.addDocument('what are the catgories', 'categories');
classifier.addDocument('can I see the categories', 'categories');

// Weather
classifier.addDocument('what is the weather', 'weather');
classifier.addDocument('how cold is it', 'weather');
classifier.addDocument('how hot is it', 'weather');
classifier.addDocument('is it raining', 'weather');


classifier.train();

Here we can see some basic training of phrases used in the classifier.  The more variants introduced the smarter the classifier becomes and the more accurate the interpretation becomes.  This yields the appearance of a cognitive ChatBot able to understand plain English rather than make use of antiquated keyword passed commands and responses akin to the telephone IVR.

I will show only a few additional code snippets because the use case for these are specific and may not apply to audience generally.  You are free to remove and modify these functions as you wish.

                 var element = {};
                 element.title = category.name;
                 element.subtitle = category.description;
                 element.default_action = {
                                          type: 'web_url',
                                          url: category.link,
                                          messenger_extensions: false,
                                          webview_height_ratio: 'tall',
                                      }
                 element.buttons = [
                                 {
                                   type: 'postback',
                                   title: 'List Articles',
                                   payload: 'category '+category.id,
                                 }
                               ]
                 elements.push(element);

Between lines 177 and 193 we see a code snippet used to populate an elements array which will be used as the contents for one of Facebook’s many messaging templates.  In this case a ‘list’ template which is demonstrated below between lines 201 and 216.  Note that on line 188 we define a postback button.

                var messageData = {
                  recipient: {
                    id: recipientId
                  },
                    message: {
                      attachment: {
                        type: "template",
                        payload: {
                          template_type: "list",
                          top_element_style: 'compact',
                          elements: elements
                        }
                     }
                   }
                };
                callSendAPI(messageData);

Above we see the messageData object formatted to include the correct message construct as per Facebook’s documentation.  At its conclusion the messageData object is passed to the callSendAPI function below.

// Takes the formatted message object and sends it to the Facebook graph
// endpoint. Here we will make use of the 'unirest' library rather than
// the standard request library for more features and flexibility.
function callSendAPI(messageData) {
            var rest = require(path.join(__dirname,'../lib/rest/client.js'));
            rest.post('https://graph.facebook.com/v2.6/me/messages', messageData, {strictSSL: false, query:{access_token: fbConfig.accessToken}}, function(err, results) {
                if (!err && results.code == 200) {
                  log.info("Successfully sent message with id %s to recipient %s", results.body.messageId, results.body.recipientId);
                } else {
                  log.error("Unable to send message");
                  log.error(results.body);
                }
          });
}

The code between lines 113 and 126 above demonstrates a new functionality in this tutorial in the ability to make external REST calls to APIs using our ‘unirest’ library.  This is an extremely power library and is used extensively so it should be a regular part of your developer toolkit.

Postbacks occur when a Facebook user clicks on a specific button defined as a postback.  In this case data – a payload – is sent back to the application webhook as a specific postback event rather than a message event and is handled between lines 390 and lines 404.

// The 'categories' function above includes an example of embedding postbacks 
// in to the messages.  This function is called when a postback is received from
// the client.
function receivedPostback(event) {
  var senderID = event.sender.id;
  var payload = event.postback.payload;
  // Some funky case statements
  switch(true) {
     case /category/.test(payload):
         sendTextMessage(senderID, 'Okay, let me retrieve that for you.');
         sendPostsMessage(senderID, null, payload.split(' ')[1])
       break;

  }
}

The last remaining demonstration lines between lines 128 and lines 153 and demonstrates the two messaging types namely a text message and a ‘sender action’.  A sender action emulates the actions of a sender so that the recipient perceives something is happening (such as the remote party typing or having seen a particular message).

// This function demonstrates how to notify the sender of message actions.
// Available options are typing_on, typing_off and mark_seen
function sendStatus(recipientId, messageStatus) {
  var messageData = {
    recipient: {
      id: recipientId
    },
    sender_action: messageStatus
  };
  callSendAPI(messageData);
}


// This function formats a generic text message very much in line with
// any ordinary facebook message.
function sendTextMessage(recipientId, messageText) {
  var messageData = {
    recipient: {
      id: recipientId
    },
    message: {
      text: messageText
    }
  };
  callSendAPI(messageData);
}

Please feel free to check out the code at YourAPIExpert’s GitHub Repository and experiment with it.  If you are setting our on the journey to customize this ChatBot you may wish to refer to the Facebook Developer Documentation for the Messenger platform.

Want This ChatBot Developed?

If you would like me to develop this ChatBot for your own application and integration please feel free to contact me here or via the popular Freelancer.com website using the details below.

Stephen Lombard

Integration And Automation Expert

Hire me on Freelancer.com

Pages: 1 2 3 4 5 6

Written by YourAPIExpert