Chapter 3. The API Messenger Platform

In the previous chapter we created a simple bot engine using Node.js and integrated with different bot platforms including the Facebook Messenger Platform. In this chapter we will learn more about the different features and API Messenger Platform offers, and how we can use them to create a bot that can work as a virtual shopping assistant for our dummy online T-shirt store.

Online T-shirt Store

Like web or mobile apps, chatbots work as an interface for our users to interact with our services. Ideally, we want to use the same backend services for all our client apps. In this example, we want to show that our shopping assistant bot can talk to an external service. We will build a mock backend for our chatbot. You can skip this section if you want to integrate our bot with any existing backend.

Let’s assume our online store’s website is using a RESTful API and we want to use the same API for our chatbot. We will start by implementing this mock backend service which will be deployed separately and our chatbot will use this API service to search and filters products and recommend to our users.

If you are new to REST API or would like to learn more about API design we highly recommend this ebook from apigee.com.

Store REST API

We will use Node.js to implement our backend service.

Create a new folder store-api, run command npm init and then following the prompts to initialize a new Node.js project and generate package.json.

mkdir store-api
cd store-api
npm init

We will use Express as our web application framework with handlebars as the view engine and body-parser middleware to parse incoming request body.

Run the following command to install these dependencies and add them to the package.json file.

npm install --save express body-parser express-handlebars

At this stage our package.json looks something like this.

{
  "name": "store-api",
  "version": "1.0.0",
  "description": "REST API for our t-shirt store",
  "main": "index.js",
  "author": "Bot Creator",
  "dependencies": {
    "body-parser": "^1.15.2",
    "express": "^4.14.0",
    "express-handlebars": "^3.0.0"    
  }
}

Create index.js file with the following content

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const hbs = require('express-handlebars').create({});

const products = require('./products');

app.set('port', (process.env.PORT || 5000));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.use(express.static('public'));
app.engine('handlebars', hbs.engine);
app.set('view engine', 'handlebars');

app.get('/products/', function (req, res) {
  res.status(200).send(filterProducts(req.query, products));
});

app.get('/products/:productId', function (req, res) {
  const productId = req.params.productId;
  const product = products.find(product => product.id === productId);
  if (product) {
    res.render('product', product)
  } else {
    res.status(404).send({ code: 404, messasge: 'NOT_FOUND' });
  }
});

app.listen(app.get('port'), function () {
  console.log('App running on port', app.get('port'))
});

const filterProducts = (query, products) => {
  let filteredProducts = products;

  if (query.gender) {
    filteredProducts = filteredProducts.filter(product => product.gender === query.gender);
  }

  if (query.size) {
    filteredProducts = filteredProducts.filter(product => {
      const availableSizes = product.availableSizes;
      return availableSizes.indexOf(query.size) > -1;
    });
  }
  return filteredProducts;
}

Let’s quickly go through the code. Our app starts a server and listens for connections on a port defined in our enviroment variable ‘PORT’ or defaults to port 5000. We then configure our app to the server static file from the ‘public’ directory and use handlebars as the view engine.

We defined two endpoints, a GET request on '/products/' will return a list of products and a GET request on '/products/:productId’ will render a view with product details of product with same id as request parameter productId.

We also defined a method filterProducts, which uses query parameters gender and size to filter and return matching products.

In a real app we would have some kind of data store to store a list of products, but to keep things simple for our mock service, we defined the product list as products.json file.

Here is what our products.json looks like.

[
  {
    "id": "1",
    "name": "Stripes T-shirt",
    "description": "White and blue stripes t-shirt",
    "gender": "M",
    "price": 100.00,
    "availableSizes": [
      "XS",
      "S",
      "M",
      "L",
      "XL"
    ]
  },
  {
    "id": "2",
    "name": "Stripes T-shirt",
    "description": "White and blue stripes t-shirt",
    "gender": "W",
    "price": 100.00,
    "availableSizes": [
      "XS",
      "S",
      "M",
      "L",
      "XL"
    ]
  },
  {
    "id": "3",
    "name": "Dotted T-shirt",
    "description": "White and red dotted t-shirt",
    "gender": "M",
    "price": 90.00,
    "availableSizes": [
      "S",
      "M",
      "L"
    ]
  },
  {
    "id": "4",
    "name": "Dotted T-shirt",
    "description": "White and red dotted t-shirt",
    "gender": "W",
    "price": 90.00,
    "availableSizes": [
      "S",
      "M",
      "L"
    ]
  },
  {
    "id": "5",
    "name": "Plain T-shirt",
    "description": "Blue plain t-shirt",
    "gender": "M",
    "price": 60.00,
    "availableSizes": [
      "XS",
      "S",
      "M",
      "L",
      "XL"
    ]
  },
  {
    "id": "6",
    "name": "Plain T-shirt",
    "description": "Blue plain t-shirt",
    "gender": "W",
    "price": 60.00,
    "availableSizes": [
      "XS",
      "S",
      "M",
      "L",
      "XL"
    ]
  }
]

We also have an image for each of our products in the ‘public/images’ directory, where the name of the image is same as the product id, for example: image for product with id 1 is ’1.png’.

Finally, we have a simple view to show product details. Create a views directory in our product root and then create product.handlebars with the following content.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>T-shirt Store</title>
    </head>
    <body>
        <h2>{{{name}}}</h2>
        <p>{{{description}}}</p>
        <img src="../images/{{{id}}}.png" height="275px" width="200px" />
        <p>Price - ${{{price}}}</p>
        <button>Buy Now</button>
    </body>
</html>

Deploy a backend service

We can deploy our app to any cloud service, but to keep things simple, we will use Heroku.

  1. If you do not have an account - sign up on heroku.com and then install Heroku CLI.
  2. Initialize a Git repository and commit the code to easily push it to heroku. Use git init to initialize and git add . followed by git commit -m "Initial commit" to commit the code.
  3. Create Procfile, a file with following content. Procfile declares what commands to run on Heroku. web: node index.js
  4. Run the heroku create command to create and setup a new app on Heroku for our backend service.
  5. Now you can run the app locally using the heroku local command.
  6. To deploy the app on Heroku use git push heroku master and use heroku open to open the deployed app in the browser.

Run the app locally and open 'http://localhost:5000/products'. You will see a list of products. To filter products, use query params, for example, if you like to search for medium size women t-shirts, try the following URL: http://localhost:5000/products?gender=W&size=M.

To see the product details view of a product with id 3, open the URL: 'http://localhost:5000/products/3'. You can also access images for a specific product, for example to see image of a product with id 5, open URL http://localhost:5000/images/5.png in the browser.

Update BOT Engine

Now that we have our store API, lets update our bot engine to interact with the users and recommend them t-shirts based on their search filters.

Welcome screen

The welcome screen is the first thing our users see when they interact with our bot. Although, it pulls the name, description, profile picture, and cover photo from the Facebook page associated with our bot, here is how we can customize it.

Before we start, it is important to note that the welcome screen only appears for new conversations, so for testing, you may want to delete the existing conversation with the bot and start a new one to see the welcome screen.

Greeting text

By default, the welcome screen shows the same description as our bot’s Facebook page, but we can override this by setting a greeting text. We can even personalise our greeting text by including a user’s name.

To set the welcome screen greeting text, we need to make a POST request to Facebook Messenger Platform with the following payload:

{
  setting_type:'greeting',
  greeting: {
    text:'Hi {{user_first_name}}, Welcome to awesome t-shirt store virtual assistant bot.'
  }

The request should include a setting_type field as greeting and a greeting object with a text field set as our greeting message. The text can be up to 160 characters long.

In the example above we are using the {{user_first_name}} template string to show the user’s first name in our greeting text. We can similarly use {{user_last_name}} and {{user_full_name}} to show the user’s last name and full name respectively.

To make an HTTP request, we can use curl command.

curl -X POST -H "Content-Type: application/json" -d '{
  "setting_type":"greeting",
  "greeting":{
    "text":"Hi {{user_first_name}}, Welcome to awesome t-shirt store virtual assistant bot."
  }
}' "https://graph.facebook.com/v2.6/me/thread_settings?access_token=PAGE_ACCESS_TOKEN"

Here, replace PAGE_ACCESS_TOKEN with the page access token we generated in chapter 2.

If we want to remove the greeting text and instead use the default page description again, all we need to do is to make a DELETE request with setting_type set as ‘greeting’.

If we use curl, our request will look like this:

curl -X DELETE -H "Content-Type: application/json" -d '{
  "setting_type":"greeting"
}' "https://graph.facebook.com/v2.6/me/thread_settings?access_token=PAGE_ACCESS_TOKEN"

Welcome screen with greeting text

Get Started button

Another thing we can do to our welcome screen to make it more user friendly is to add a ‘Get Started’ button. When a user clicks on this button, our bot engine will receive a postback received callback that we can then use to display a welcome or a help message to the user.

To add the ‘Get Started’ button, make a POST request to the Facebook Messenger Platform with the following payload. We can use the curl command to make the POST request, so please refer to the greeting text section for details.

{
  setting_type: 'call_to_actions',
  thread_state: 'new_thread',
  call_to_actions :[
    {
      payload: 'GET_STARTED_BUTTON_CLICKED'
    }
  ]
}

Our request should include the setting_type field set to call_to_actions and the thread_state field set to new_thread. It should also include an array call_to_actions, with only one element and the element object itself, should have a payload field.

We are setting the payload to GET_STARTED_BUTTON_CLICKED to inform our bot engine that the user clicked the ‘Get Started’ button. We will implement the code to handle postback callback in the next section.

To remove the button, make a DELETE request with following payload:

{
  setting_type: 'call_to_actions',
  thread_state: 'new_thread'
}

Welcome screen with get started button

Now when we start a new conversation with out bot, we will see the welcome screen with a custom greeting text and ‘Get Started’ button.

Text message

After setting up our welcome screen, let’s update our bot engine to handle the postback received callback and show a welcome message to our user. We will use the bot engine app we created in chapter 2.

We will use axios to make http requests. Axios is a promised based http client. Read more about axios. To install axios and add to our package.json as a dependency, run the following command:

npm install --save axios

We want to use the index.js file in our project’s root directory as an entry point to our application and keep all of our bot engine related code in a separate directory.

Update index.js with following code:

const app = require('express')();
const bodyParser = require('body-parser');
const botEngine = require('./src/bot-engine');

const VALIDATION_TOKEN = 'super_secret_validation_token';

app.set('port', (process.env.PORT || 5000));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.get('/webhook', function (req, res) {
    if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === VALIDATION_TOKEN) {
        res.status(200).send(req.query['hub.challenge']);
    } else {
        res.sendStatus(403);
    }
});

app.post('/webhook/', function (req, res) {
    botEngine.handleIncomingMessage(req.body.entry);
    res.sendStatus(200); 
});

app.listen(app.get('port'), function () {
    console.log('App running on port', app.get('port'))
});

Our index.js starts our app and listens for connections. It still handles GET requests to verify our webhook and also receives POST requests but now delegates all of the incoming messages to our bot engine.

Create a new folder ’src’ in our project root and inside this folder create a new file bot-engine.js with the following code:

const axios = require('axios');

const PAGE_ACCESS_TOKEN = process.env.PAGE_ACCESS_TOKEN;
const BASE_FB_URL = 'https://graph.facebook.com/v2.6';
const FB_MESSENGER_URL = `${BASE_FB_URL}/me/messages?access_token=${PAGE_ACCESS_TOKEN}`;

const postMessage = (recipientId, message) => {
  return axios.post(FB_MESSENGER_URL, {
    recipient: { id: recipientId }, message
  })
  .catch(function (error) {
    console.error(`Unable to post message to Facebook ${message}`, error);
  });
}

const handleIncomingMessage = (entries) => {
  entries.forEach(function (entry) {
    entry.messaging.forEach(function (event) {
      let sender = event.sender.id

      if (event.postback) {
        const { payload } = event.postback;
        if (payload === 'GET_STARTED_BUTTON_CLICKED') {
        postMessage(sender, { text: `Hi, Welcome to our awesome online T-shirt store` });
        }
      }
    });
  });
}

module.exports = {
  handleIncomingMessage
}

In our bot-engine.js, we have a function handleIncomingMessage, which we are exposing using module.exports.

This function loops through all the incoming entries and in each entry loop through all of the message events. If an event is a postback receieved callback, we check the payload and if the payload matches what we defined for the ‘Get Started’ button, we send a message to our user using the postMessage function. The postMessage function uses axios to make a POST request to Messenger Platform with recipient and message object.

While making a POST request to the Messenger Platform, we need to provide our page access token as part of the URL. Instead of hard coding the page token within our code, we can define it as an environment variable. We can set the environment variable for our Heroku app using their command line tool or through the web dashboard under the app’s settings tab. Visit this link for more details.

To test if our bot is working as expected, push our to code to the server. If you haven’t setup the Heroku app for our bot, follow the steps we used to deploy our backend service.

Now if we start a new conversation with our bot and click on the ‘Get started’ button, our bot will respond with a welcome message.

Welcome message

User Profile

We pesonalised the greeting text on our welcome screen, similarly we can personalise our welcome message as well with user details.

Facebook offers a User Profile API to retrieve user details. This API can be used to retrieve a user’s first name, last name, profile picture URL, locale, timezone, gender, and more.

To retrieve user details, make a GET request to https://graph.facebook.com/v2.6/<USER_ID>?access_token=PAGE_ACCESS_TOKEN. Here, <USER_ID> is the sender’s id and PAGE_ACCESS_TOKEN is the page access token we generated before.

We can also specific which user profile fields we are interested in as query parameters while making the request.

Add the following code to bot-engine.js:

const retrieveUserDetails = (userId) => {
  return axios.get(`${BASE_FB_URL}/${userId}?fields=first_name&access_token=${PAGE_ACCESS_TOKEN}`)
    .catch(function (error) {
      console.error(`Unable to user details ${userId}`, error);
    });
}

In the retrieveUserDetails function, we are making the GET request to retrieve the user’s first_name.

Please note that this API is only available after a user’s initial conversation with our bot, and if not, we will receieve an empty response.

Update the code inside handleIncomingMessage to personalize our welcome message and include the user’s first name.

if (payload === 'GET_STARTED_BUTTON_CLICKED') {
          const userDetails = retrieveUserDetails(sender).
            then(response => {
              const firstName = response.data.first_name;
              postMessage(sender, { text: `Hi ${firstName}, Welcome to our awesome online T-shirt store.` });
            });
        }

Personalized welcome message

Now when we click on the ‘Get Started’ button we will get a personalized welcome message.

Message with attachments

We are using the postMessage function to send a message back to our users. The function makes a POST request with a recipient and message object.

The message object for a plain text message includes a text field. This text must be UTF-8 and can be up to 640 characters long.

If we want to send an image, audio or video file to the user, we have to send a message with an attachment.

To send an attachment, the message object in our request should have an attachment object instead of a text field. The attachment object should have a type and payload field. The type can be image, audio, video or file, and the payload should be the URL of the resource. Instead of providing a URL, we can also directly upload the file and send it as an attachment to the user.

Here is a sample request to send an audio file:

recipient: {
    id: 'USER_ID'
  },
  message:{
    attachment: {
      type: 'audio',
      payload: {
        url: 'https://<AUDIO_FILE_URL>'
      }
    }
  }

We will use a message with an attachment in the next section.

Structured Message

Before we start suggesting T-shirts to our users, we want to know what they are looking for. The first thing we want to know is if they are looking for Women or Men’s T-shirts. We can update our welcome message to give these options to our user and let them make their selection.

Facebook Messenger Platform provides various pre-defined templates to send structured messages. Some of these templates have specific use case, like receipt template, to show order confirmation with transaction summary or airlines related templates to show boarding pass, checkin, itinerary or flight updates. There are also more generic templates, like list template to show a vertical list of items, button templates to show some text with buttons, and a generic template to show a message with an image, title, subtitle and some buttons. We will use a generic template later to show a list of products.

In our welcome message, we just want to show the message with some text and a couple of buttons, so we will use the button template.

Here is the request object to show a button template message with welcome text and buttons for gender selection.

recipient: {
    id: 'USER_ID'
  },
  message:{
    {
      attachment: {
        type: 'template',
        payload: {
          template_type: 'button',
          text: 'Hi, Welcome to our awesome online T-shirt store. What are you looking for today ?',
          buttons:
          [
            {
              type: 'postback',
              title: 'Women',
              payload: 'WOMEN_OPTION_SELECTED'
            }, {
              type: 'postback',
              title: 'Men',
              payload: 'MEN_OPTION_SELECTED'
            }
          ]
        }
      }
    }
  }

For the button template, our message attachment type should be ‘template’ and the attachment payload should include template_type field set to ‘buttons', a text field set to our welcome text, and an array of buttons.

Facebook Messenge platform supports different button types, and for this example we will use the postback button type. A postback button should have a title, a type set to postback, and a payload similar to the ‘Get Started’ button payload. Another commonly used button type is web_url. The Web URL button should have a title, a type field set to web_url and a URL field. This URL will be opened in the in-app browser when a user clicks on the button.

You can read more about other buttons types here

Let’s update our bot-engine.js to send a structured message as a welcome message. Create a new function sendWelcomeMessage with following code:

const sendWelcomeMessage = (sender) => {
  const userDetails = retrieveUserDetails(sender).
    then(response => {
      const firstName = response.data.first_name;
      const title = `Hi ${firstName}, welcome to awesome online T-shirt shop. What are you looking for today ?`;
      const message = {
        attachment: {
          type: 'template',
          payload: {
            template_type: 'button',
            text: title,
            buttons:
            [
              {
                type: 'postback',
                title: 'Women',
                payload: 'WOMEN_OPTION_SELECTED'
              }, {
                type: 'postback',
                title: 'Men',
                payload: 'MEN_OPTION_SELECTED'
              }
            ]
          }
        }
      };
      postMessage(sender, message);
    });
}

Now update the handleIncomingMessage function to call the sendWelcomeMessage function when the payload of a postback recieved callback is equal to GET_STARTED_BUTTON_CLICKED.

...

if (payload === 'GET_STARTED_BUTTON_CLICKED') {
  sendWelcomeMessage(sender);
}

...

Now our welcome message will show a structured message with two buttons.

Structured message with button template

Quick replies

The next thing we want to know is what T-shirts size our user is looking for. We want to keep all of the code related to our store API in a separate file. Create a new file store-api.js under the src directory with the following code:

const sizes = [
  { label: 'Extra Small', value: 'XS' },
  { label: 'Small', value: 'S' },
  { label: 'Medium', value: 'M' },
  { label: 'Large', value: 'L' },
  { label: 'Extra Large', value: 'XL' }
];

const genders = [{ label: 'Men', value: 'M' }, { label: 'Women', value: 'W' }]

module.exports = {
  getSizes: sizes,
  getGender: genders
}

Here we have sizes and gender arrays to size and gender options. Each array object has a label and a value field. We will use the label in our message and value when we call our backend service.

Gender selection was part of our welcome message and we used a button template for it. The button template only allows a maximum of three buttons, so we cannot use that to show size options to our users.

We can use another structured message type offered by Messenger Platform called quick replies. Quick replies are shown close to the composer, prompting the user to use these options to make a quick selection. Once the user makes a selection, the options disappear.

The POST request payload to show quick replies can have either a text or an attachment field and a list of quick reply buttons. We can send a maximum of 11 quick reply buttons with each message.

Each quick reply button should have a content_type that can be text or location. If the content_type is text, title and payload fields are required. Here the title will be used as button title and can be up to 20 characters, and the payload will be used to send the selection back to our bot engine. We can also set an optional image_url field for a quick reply button.

A quick replies type ‘location’ can be used by the user to share their location. In case of a postback type button, the Messenger Platform uses a postback received callback to send the button payload to our bot engine.

In the case of quick replies, when a quick reply button is tapped, the Messenger platform will send a message to our bot with a text field with the same value as the title of the button clicked, and a quick_reply object with a payload field with the same value as the payload of the button. If the context_type of the quick reply button is location, the payload will contain the coordinates of the user.

Read more about quick replies here.

Lets first update our code to handle gender selection.

if (event.postback) {
    const { payload } = event.postback;
    if (payload === 'GET_STARTED_BUTTON_CLICKED') {
      sendWelcomeMessage(sender);
    } else if (payload === 'WOMEN_OPTION_SELECTED' || payload === 'MEN_OPTION_SELECTED') {
      sendSizeOptions(sender);
    }
}

Now if we receieve a postback callback with payload equals to ‘WOMEN_OPTION_SELECTED’ or ‘MEN_OPTION_SELECTED', we will call the sendSizeOptions function.

Add sendSizeOptions function to send T-shirts size options as quick replies message.

const storeApi = require('./store-api');

const sendSizeOptions = (sender) => {
    const buttons = storeApi.getSizes.map(function (option) {
        return {
            content_type: 'text',
            title: option.label,
            payload: JSON.stringify({ size: option.value })
        }
    });
    const message = {
        text: `Please select a size.`,
        quick_replies: buttons
    }
    postMessage(sender, message);
}

Here we import the store-api to get the size options and use these options to build an array of buttons with content_type 'text', title set to the option label, and a payload set to the JavaScript object with a key size and value the same as the size option value. Since the payload field expects a string, we convert the object to string using the JSON.stringify function.

Quick replies

Now when we select one of the gender options, our bot will respond with a quick replies message with T-shirt size options.

Generic template

Now the user can select the gender and size they are looking for, so let’s show them a list of T-shirts matching their search criteria.

We will start by updating our store API and add a method to retrieve products by calling our REST API.

Update store-api.js with following code:

const axios = require('axios');

const API_BASE_URL = process.env.API_BASE_URL;
const GET_PRODUCTS_API_URL = `${API_BASE_URL}products`;

const sizes = [
  { label: 'Extra Small', value: 'XS' },
  { label: 'Small', value: 'S' },
  { label: 'Medium', value: 'M' },
  { label: 'Large', value: 'L' },
  { label: 'Extra Large', value: 'XL' }
];

const genders = [{ label: 'Men', value: 'M' }, { label: 'Women', value: 'W' }]

const retriveProducts = () => {
  return axios.get(GET_PRODUCTS_API_URL)
    .then(response => {
      return response.data.map(product => {
        return Object.assign({}, product, {
          imageUrl: `${API_BASE_URL}images/${product.id}.png`,
          url: `${GET_PRODUCTS_API_URL}/${product.id}`,
        });
      });
    })
    .catch(function (error) {
      console.error('Unable to retrieve products', error);
    });
}

module.exports = {
  getSizes: sizes,
  getGender: genders,
  retriveProducts
}

As you can see, we are loading the API_BASE_URL from the environment variable. This is the URL of our store API Heroku app. Set this environment variable locally and on Heroku.

In the retriveProducts function, we are making a GET request to our store API products endpoint. At this stage we are not applying any filters, we just want to retrieve all of the products for now. The function also adds two new fields, imageUrl and url, to each matching product returned by our backend service. The imageUrl field is set to the URL of the product image, and the url field is set to the URL of the product details page. These URLs are constructed based on our backend service implementation. Please refer to the Store REST API section for more details.

The next step is to update our handleIncomingMessage function to send a message to our user with a list of products, after they have selected the T-shirt size.

const handleIncomingMessage = (entries) => {

  entries.forEach(function (entry) {
    entry.messaging.forEach(function (event) {
      let sender = event.sender.id
      if (event.message) {
        if (event.message.quick_reply) {
         sendMatchingProducts(sender);
        }
      }

      if (event.postback) {
        const { payload } = event.postback;
        if (payload === 'GET_STARTED_BUTTON_CLICKED') {
          sendWelcomeMessage(sender);
        } else if (payload === 'WOMEN_OPTION_SELECTED' || payload === 'MEN_OPTION_SELECTED') {
          sendSizeOptions(sender);
        }
      }
    });
  });
}

As we discussed before, our bot engine will receive a message receieved callback when a user selects a quick reply option. So we update our code to call the sendMatchingProducts method when we receieve a messaging event with a quick_reply object.

Finally, add the sendMatchingProducts function:

const sendMatchingProducts = (sender) => {
  storeApi.retriveProducts().then(response => {
    const message = createProductList(response);
    postMessage(sender, message);
  });
}

function createProductList(products) {
  const elements = products.map(product => {
    return {
      title: product.name,
      subtitle: product.description,
      image_url: product.imageUrl,
      item_url: product.url,
      buttons: [{
        type: "web_url",
        url: product.url,
        title: "Buy NOW"
      }],
    }
  });
  return {
    attachment: {
      type: "template",
      payload: {
        template_type: "generic",
        elements
      }
    }
  };
}

This function will use our storeApi to retrieve a list of products, create a generic template message for our product lists, and then send this message to the user.

Search result

Now when we select a T-shirt size, we will see list of T-shirts.

Session management/Caching

Our store API /products endpoint takes a list of query parameters to filter products. In a web or mobile app, user can select various filter options and we will make a single request to our backend service to get the matching products. However, when conversing with our bot, the user first selects the gender and then selects the size they are looking for. We need to store these values and then use them to filter and retrieve products matching a user’s search critiera. We need some kind of session management to store a user’s conversation and interaction with our bot.

In this example, we will use Redis for session management. Redis is an open source, in-memory key value store. It is really fast, as the read and write operations are performed in-memory. You can read more about Redis here.

Heroku offers a Redis add-on that is very easy to install and use with Heroku apps. Simply go to the Heroku dashboard, select our bot engine app, then click on the ‘Configure Add-ons’ button, and finally search and select ‘Heroku Redis’ add-on. You will be prompted to select a plan. For our bot app, we will just select the free plan. Click on the provision button and we are good to go. Heroku will automatically create an environment variable for our app’s Redis server URL, REDIS_URL, which we will later use in our app.

To interact with our Redis server, we need a Redis client. Lets add Redis Node.js client and Bluebird to our app. Bluebird is a promise library that we will use with the Redis Node.js client. You can read more about Bluebird here.

npm install --save bluebird redis

Now, create a new file session.js with the following code:

const redis = require('redis');
const bluebird = require('bluebird');
bluebird.promisifyAll(redis.RedisClient.prototype);
bluebird.promisifyAll(redis.Multi.prototype);

const client = redis.createClient(process.env.REDIS_URL);

const setData = (userId, data) => {
    return client.setAsync(userId, JSON.stringify(data));
}

const getData = (userId) => {
    return client.getAsync(userId).then(response => JSON.parse(response));
}

module.exports = {
    getData: getData,
    setData: setData
}

In our session.js, we create a Redis client using a createClient function and pass our Redis server URL that Heroku added as an environment variable.

The setData methods takes a user id and session data as parameters, and calls the Redis client to use userId as a key to store the data. The getData method can be used to retrieve the session data stored for a given userId.

Now we need to update our bot engine to store the options selected by the user and then use these options to retrieve matching products.

Add the following code to bot-engine.js:

const session = require('./session');

const updateUserFilter = (userId, newData) => {
  return session.getData(userId).then((userData) => {
    if (!userData) {
      userData = { filter: {} };
    }
    userData.filter = Object.assign({}, userData.filter, newData);
    session.setData(userId, userData);
    return userData.filter;
  });
}

Also, replace the handleIncomingMessage function with the following code:

const handleIncomingMessage = (entries) => {
  entries.forEach(function (entry) {
    entry.messaging.forEach(function (event) {
      let sender = event.sender.id
      if (event.message && event.message.quick_reply) {
        handleQuickReplies(sender, event.message.quick_reply);
      } else if (event.postback) {
        handlePostback(sender, event.postback);
      }
    });
  });
}

const handleQuickReplies = (sender, quickReply) => {
  if (quickReply && quickReply.payload) {
    const value = JSON.parse(quickReply.payload);
    updateUserFilter(sender, value)
      .then(filter => {
        sendMatchingProducts(sender, filter);
      });
  }
}

const handlePostback = (sender, postback) => {
  const { payload } = postback;
  if (payload === 'GET_STARTED_BUTTON_CLICKED') {
    sendWelcomeMessage(sender);
  } else {
    const value = JSON.parse(payload);
    updateUserFilter(sender, value)
      .then(filter => {
        sendSizeOptions(sender);
      });
  }
}

Just like with our sendSizeOptions function, we want to use the gender options from store-api instead of the hard coded values. So we break the sendWelcomeMessage function and create two new methods: buildButtonTemplateMessage function to build the button template message with a given text and array of buttons, and the getGenderOptionButtons function to return a list of postback buttons for gender selection.

const sendWelcomeMessage = (sender) => {
  const userDetails = retrieveUserDetails(sender).
    then(response => {
      const firstName = response.data.first_name;
      const title = `Hi ${firstName}, welcome to awesome online T-shirt shop. What are you looking for today ?`;
      postMessage(sender, buildButtonTemplateMessage(title, getGenderOptionButtons()));
    });
}

const buildButtonTemplateMessage = (text, buttons) => {
  return {
    attachment: {
      type: 'template',
      payload: {
        template_type: 'button',
        text,
        buttons
      }
    }
  };
}

const getGenderOptionButtons = () => {
  return storeApi.getGender.map(function (option) {
    return {
      type: 'postback',
      title: option.label,
      payload: JSON.stringify({ gender: option.value })
    }
  });
}

And finally, update the sendMatchingProducts function:

const sendMatchingProducts = (sender, filter) => {
  storeApi.retriveProducts(filter).then(response => {
    const message = createProductList(response);
    postMessage(sender, message).then((response) => {
      showMessageToStartNewSearch(sender);
    });
  });
}

Let’s go through all of these changes. First, we import our session.js and add the updateUserFilter function. This function retrieves a user’s session data, updates it with new data, and stores it back in our Redis server.

Next, we refactored the handleIncomingMessage function to make it more readable. Now we have separate functions to handle quick replies and postback received callbacks.

At the moment, our bot engine can only receive postback callbacks on the ‘Get Started’ button click or when a user selects a gender option. When a user makes a gender selection, our handlePostback function will pass the user id and the received payload to the updateUserFilter function to store selected gender in user session. Similarly, the handleQuickReplies function is using updateUserFilter to store the selected size and then pass the updated filter, or the selected gender and size, to the sendMatchingProducts function. The sendMatchingProducts function is updated to pass the filter to storeApi retriveProducts function.

Let’s update retriveProducts in store-api.js to pass the filter object properties as query parameters while making GET requests to our backend service.

const retriveProducts = (filters) => {
  return axios.get(GET_PRODUCTS_API_URL, {
    params: filters
  }).then(response => {
    return response.data.map(product => {
      return Object.assign({}, product, {
        imageUrl: `${API_BASE_URL}images/${product.id}.png`,
        url: `${GET_PRODUCTS_API_URL}/${product.id}`,
      });
    });
  }).catch(function (error) {
    console.error('Unable to retrieve products', error);
  });
}

Filtered search result

Now when we select a gender and a size, our bot will only suggest T-shirts matching our selected critieria.

After showing the search results, we want to give users the option to start another search.

Let’s add a new method, sendOptionToStartNewSearch:

const sendOptionToStartNewSearch = (sender) => {
  const buttons = [{
    type: 'postback',
    title: 'Find more T-shirts',
    payload: 'START_NEW_SEARCH'
  }];
  postMessage(sender, buildButtonTemplateMessage('Click here to start another search.', buttons));
}

In this method, we are reusing our buildButtonTemplateMessage method to send a button template message to our user with some text and a single postback button to start a new search.

Update the sendMatchingProducts function to call this method after the product list message is sent successfully to the user.

const sendMatchingProducts = (sender, filter) => {
  storeApi.retriveProducts(filter).then(response => {
    const message = createProductList(response);

    postMessage(sender, message).then((response) => {
      sendOptionToStartNewSearch(sender);
    });
  });
}

We also need to update the handlePostback function to start a new search postback button callback:

const handlePostback = (sender, postback) => {
  const { payload } = postback;
  if (payload === 'GET_STARTED_BUTTON_CLICKED') {
    sendWelcomeMessage(sender);
  } else if (payload === 'START_NEW_SEARCH') {
    session.setData(sender, {});
    postMessage(sender, buildButtonTemplateMessage('What are you looking for ?', getGenderOptionButtons()));
  } else {
    const value = JSON.parse(payload);
    updateUserFilter(sender, value)
      .then(filter => {
        sendSizeOptions(sender);
      });
  }
}

Search again

Now, after showing the filtered search results, we give the user the option to start a new search.

Sender actions

In a mobile or web app, we use loading spinner to give visual feedback to the users that we are performing a task and waiting for response. In case of chat clients this is done by a tick to show that the message is received by the recipient, or by typing an indicator to show the recipient is typing or preparing a response.

When we call our REST API to retrieve matching products, the backend service may take some time to respond. Let’s say we want to show a visual indicator to our users that we are processing their request. We can do this by using Facebook Messenger Platform’s Send API.

We make a POST request with recipient object, similar to using the postMessage function, but instead of a message object, we include a sender_action field. This field supports following values: mark_seen to mark last message as read, typing_on to show the typing indicator, and typing_off to hide the typing indicator.

Let’s create a new method postSenderAction in our bot-engine.js:

const postSenderAction = (sender, action) => {
  axios.post(FB_MESSENGER_URL, {
    recipient: { id: sender },
    sender_action: action 
  }).catch(function (error) {
    console.error('Unable to post message to Facebook', error);
  });
}

Now update our sendMatchingProducts function.

const sendMatchingProducts = (sender, filter) => {
  postSenderAction(sender, 'typing_on');
  storeApi.retriveProducts(filter).then(response => {
    const message = createProductList(response);
    postMessage(sender, message).then((response) => {
      showMessageToStartNewSearch(sender);
      postSenderAction(sender, 'typing_off');
    });
  });
}

We send action typing_on before we call our backend service to retrieve products and then send action typing_off once we receive response from the service.

Persistent Menu

We want to provide our users the option to visit our website and to start a new search anytime. We want to add a menu that is always available to the users. This can be done by using Persistent Menu.

Similar to the greeting message or the ‘Get Started’ button, the persistent menu can be added by making a POST request to Facebook Messenger Platform.

Make a POST request with the following body:

{
  setting_type : 'call_to_actions',
  thread_state : 'existing_thread',
  call_to_actions:[
      {
      type: 'web_url',
      title: 'Visit our website',
      url: 'http://<our-website-url>'
    },
    {
      type: 'postback',
      title: 'Start a new search',
      payload: 'START_NEW_SEARCH'
    }
  ]
}

The request should include a setting_type field set to call_to_actions, a thread_state field set to existing_thread, and an array object call_to_actions with a list of menu items. Each menu item should have a type field set to either postback or web_url and a title field of up to 30 characters. The postback type item should have a payload field and the web_url type item should have a URL field.

The payload we are using for the postback button is the same as the payload for our start new search button. So when the user clicks on this menu option, our bot will start a new search.

Persistent Menu

Now you will be able to see a three caret icon next to the composer. Click on the icon to open the persistent menu.

To remove tthe persistent menu, make a DELETE request with the following payload:

{
  setting_type:'call_to_actions',
  thread_state:'existing_thread'
}

Summary

In this chapter we extended our bot to work as a shopping assisant for an online T-shirt store. We learned about different features and APIs Messager Platform offers to build a bot with great user experience. Our users can easily interact with our bot using structured messages we implemented in this chapter. However, we want to make our bot more conversational. We want to allow the users to interact with our bot just the same way they interact with other humans.

In the next chapter, we will make our bot smarter. We will learn about Natural Language Processors and how to use Facebook’s natural processing tool Wit.ai to make our bot more conversational.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset