Step By Step Guide

Step by step Guide

In this guide, we will walk you through the advanced customization options of the Inbenta Chatbot. To do this, we will create two adapters: the first one will communicate with a third-party API, and the second one will sanitize messages. Let's go!

The Brewery API

The first adapter that we create makes a request to a third-party API instead of the Inbenta API only when the user message includes the word "beer". The bot processes the response to display the description parameter as a Chatbot message and use the image_url parameter to load the image in the Side Window.

The sanitizer

Depending on the order the adapters are inserted in the adapter array, our second adapter will either sanitize the messageData.message in the onSendMessage callback, the message sent to the Inbenta API, or both. We call sanitizing the replacement of certain words with non-meaningful characters. This can be useful for example when you want to hide sensitive information.

Customizing with configuration

The easiest way to customize the Inbenta Chatbot is with the configuration. However, in cases like our example, we want to modify the behavior of the Chatbot when it displays a user message or sends it to the Inbenta API. Therefore, configuration is not the best option in this situation.

Customize with subscriptions

Since we modify the behavior of the Chatbot, we must subscribe some callbacks for specific actions. It is from within these callbacks that we will modify the default behavior of the Chatbot.

We start with the Brewery API. We need to check if the message that the user wrote contains the word "beer". If this is the case, we send the message to the Brewery API, otherwise we do not modify the default behavior.

To do this, we subscribe a callback to onSendMessage like this:

bot.subscriptions.onSendMessage(
  function(messageData, next) {
    if (!/beer/.test(messageData.message)) {
      return next(messageData);
    }

    const matchedBeer = messageData.message.match(/\b(ipa|punk|brown|blonde)\b/);
    const beer = (matchedBeer) ? matchedBeer[1] : 'ipa';

    // We use jQuery for doing an AJAX request, you can use your preferred library (even the natives XMLHttpRequest or fetch APIs)
    $.ajax({
      url: 'https://api.punkapi.com/v2/beers?beer_name=' + beer,
      success: function(data){
        var message = data[0].description;
        var title = data[0].name;
        var sideWindowimg = '<img src=' + data[0].image_url + ' width="330" height="300">';
        var SideWindow = {
          sideWindowContent: sideWindowimg,
          sideWindowTitle: title,
        }
        var beerResponseMessage = {
          type: 'answer',
          message: message,
          sideWindowTitle: SideWindow.sideWindowTitle,
          sideWindowContent: SideWindow.sideWindowContent
        }

        bot.actions.displayChatbotMessage(beerResponseMessage);
        bot.actions.showSideWindow(SideWindow);
      }
    })
  }
);

At the beginning of the callback, we check if the message contains "beer". If it does not, we execute the next function: we pass messageData as its argument and return its value. This means that we execute the next callback and finish our callback execution. After a return statement, no more code of the current function is executed.

If the word "beer" exists in the message, the callback does not call the next function. This way, we avoid the default sendMessage action (that sends the message to the Inbenta API). We also make an AJAX request. If it is successful, it dispatches two actions: displayChatbotMessage and showSideWindow.

The result looks like this:

custom_icon_custom_name

After this, we create our sanitizing subscription. We must check if the message contains any sanitized words and if it does, replace them with other characters. In the following example, we will use a hardcoded regex (a simple regex for Spanish mobile phone numbers) and replacement characters. We subscribe it to the sendMessage, so we will sanitize only the message sent to the Inbenta API, but not the displayed user message.

var phoneRegex = /(\+34|0034)\d{9}/;

bot.subscriptions.onSendMessage(
  function(messageData, next) {
    if (phoneRegex.test(messageData.message)) {
      messageData.message = messageData.message.replace(phoneRegex, '******');
    }

    return next(messageData);
  }
);

If there is a match between our phone regex and the message, we modify the message (we replace the phone number with '******'). Then, we jump to the next callback (calling the next function with the messageData) and finish our callback.

If the phone regex does not match, we jump to the next callback directly.

The result looks like this: sanitize

Location of the subscriptions

We must create our subscriptions inside an adapter. Since our subscribed callbacks are independent from each other, we place each subscription inside a different adapter. Then, we pass these adapters to the configuration of the build.

Important: We must pay attention to the order in which we use our adapters. Here is an in-depth look to see it in detail:

function beerAdapter(bot) {
  bot.subscriptions.onSendMessage(
    function(messageData, next) {
      // all beer stuff in the previous section
    }
  );
}

function sanitizerAdapter(bot) {
  bot.subscriptions.onSendMessage(
    function(messageData, next) {
      // all sanitizing stuff in the previous section
    }
  );
}

var authorization = {
  domainKey:"<example_domain_key>",
  inbentaKey:"<example_inbenta_key>"
}
InbentaChatbotSDK.buildWithDomainCredentials(authorization, {
  adapters: [
    beerAdapter,
    sanitizerAdapter
  ]
});

In the above example, the first adapter that executes is the beerAdapter, so its onSendMessage callback is the first to be executed when the sendMessage action is dispatched.

If the message contains the word "beer" and a number that matches the phone regex, only the beerAdapter callback is executed. Neither the sanitizerAdapter callback nor the default sendMessage action are executed. This is because the beerAdapter callback does not call the next function.

If the message does not contain the word "beer", but contains a number that matches the phone regex, the sanitizerAdapter callback modifies the message and executes the default sendMessage action.

Now, we use our adapter like this:

InbentaChatbotSDK.buildWithDomainCredentials(authorization, {
  adapters: [
    sanitizerAdapter,
    beerAdapter
  ]
});

In this case, the sanitizerAdapter callback is executed first when the sendMessage action is dispatched.

If the message contains the word "beer" and a number that matches the phone regex, the sanitizerAdapter callback modifies the message (it replaces the phone number with '******'). Then, the beerAdapter callback sends a request to the Brewery API.

In summary, in our first example, it is impossible to sanitize the message in the beerAdapter callback because of the order of adapters. In other cases where the message contains only the phone regex matches or only the word "beer", we obtain the same result with both examples.

Location of the adapter definition

There are no restrictions in terms of where to declare and define adapters. Inbenta recommends that you place your adapters in a different js file and import this file in a script tag. You can then pass the adapters you want, in any order, in the adapters array.

// my-adapters.js
function beerAdapter(bot) {
  // the beer adapter code
}

function sanitizerAdapter(bot) {
  // the sanitizer adapter code
}
// index.html
<!DOCTYPE html>
<html>
<head>
  <title>My adapters demo</title>
  <script src="https://sdk.inbenta.io/chatbot/1/inbenta-chatbot-sdk.js"></script>
  <!-- we import jquery for the beer adapter, recall that you can use any other library -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script src="my-adapters.js"></script>
</head>
<body style="background-color:#eee;">
  <div>
    Hello Chatbot!
  </div>
</body>
<script type="text/javascript">
  // remember to replace <your_Domain_Key> with a valid domain key and <your_API_Key> with the inbenta key.
  var authorization = {
    domainKey:"<example_domain_key>",
    inbentaKey:"<example_inbenta_key>"
  }

  InbentaChatbotSDK.buildWithDomainCredentials(authorization, {
    adapters: [
      beerAdapter,
      sanitizerAdapter
    ]
  });
</script>
</html>

Passing a configuration to an adapter

Inbenta recommends that you place your adapters in a standalone js file. One benefit of this approach is that you can reuse this file in different places, or even different projects. However, you may find that your adapters need to have some sort of configuration.

We will take the Sanitizer adapter as an example. We will use it with different regexs (not the hardcoded phoneRegex), different replacement strings, and we can even select whether we only want to subscribe it to the onDisplayUserMessage or to the onSendMessage.

The best way to create a configuration is to create a curry function. A curry function is a function that returns another function. The first function contains the configuration arguments. The returned function has the Chatbot argument, and it is actually the adapter.

function sanitizerAdapterCreator(configuration) {
  // do some stuff with the configuration argument
  return function sanitizerAdapter(bot) {
    // our sanitizerAdapter function
    // we will expand it with configuration later
  }
}

Then, call this adapter creator function in the adapters array with the configuration:

  var authorization = {
    domainKey:"<example_domain_key>",
    inbentaKey:"<example_inbenta_key>"
  }

  InbentaChatbotSDK.buildWithDomainCredentials(authorization, {
  adapters: [
    beerAdapter,
    sanitizerAdapterCreator(sanitizeConfiguration)
  ]
});

We use the adapter creator backbone and implement it in the Sanitizer adapter. In our Sanitizer adapter creator, we use an object with different properties as configuration parameters.

First, it validates the configuration (regex property is mandatory, replaceWith is optional) before it returns the adapter function:

var defaultConfig = {regex: /(\+34|0034)\d{9}/, replaceWith: '*****'};

if (typeof configuration !== 'object') {
  console.error('The SanitizerAdapter needs a configuration object! The default configuration will be used.');
  configuration = defaultConfig;
}
if (!configuration.regex || !(configuration.regex instanceof RegExp)) {
  console.error('The SanitizerAdapter configuration parameter needs a property called regex, which must be a regex! The default configuration will be used.');
  configuration.regex = defaultConfig.regex;
}
var replaceWith = configuration.replaceWith || defaultConfig.replaceWith;

We need to reshape the original subscription callback with the configuration parameters:

bot.subscriptions.onSendMessage(
  function(messageData, next) {
    if (configuration.regex.test(messageData.message)) {
      messageData.message = messageData.message.replace(configuration.regex, configuration.replaceWith);
    }

    return next(messageData);
  }
);

Then, we put it all together in our Sanitizer adapter creator:

function sanitizerAdapterCreator(configuration) {
  var defaultConfig = {regex: /(\+34|0034)\d{9}/, replaceWith: '*****'};

  if (typeof configuration !== 'object') {
    console.error('The SanitizerAdapter needs a configuration object! The default configuration will be used.');
    configuration = defaultConfig;
  }
  if (!configuration.regex || !(configuration.regex instanceof RegExp)) {
    console.error('The SanitizerAdapter configuration parameter needs a property called regex, which must be a regex! The default configuration will be used.');
    configuration.regex = defaultConfig.regex;
  }
  var replaceWith = configuration.replaceWith || defaultConfig.replaceWith;

  return function sanitizerAdapter(bot) {
    bot.subscriptions.onSendMessage(
      function(messageData, next) {
        if (configuration.regex.test(messageData.message)) {
          messageData.message = messageData.message.replace(configuration.regex, configuration.replaceWith);
        }

        return next(messageData);
      }
    );
  }
}

We can now use the Sanitizer adapter creator with different regexs and replacement strings.

For the sake of simplicity, let's assume that we only want to use the Sanitizer adapter creator:

// ... in the build method
adapters: [
  // use a very simple email regex
  sanitizerAdapterCreator({regex: /\S+@\S+\.\S+/, replaceWith: '***@***'}),
]

// or

adapters: [
  // use a Twitter user name simple regex, replace with empty string
  sanitizerAdapterCreator({regex: /^@?(\w){1,15}$/, replaceWith: ''}),
]