Start building your own chatbot now >

The purpose of the Bot Connector

Gather round, gather round, today you’re going to learn how to create your own integration for the Recast.AI Bot Connector.

But wait, what is Bot Connector ?

When you create a bot and want to show it to the entire world, you used to have only one option : connecting your bot directly to the channel of your choice (Kik, Messenger, Slack, Telegram, etc). Depending on the documentation of the channel, it can be quite easy!

But if you want to connect your bot to an additional channel, that’s when the troubles begin: code duplication, compatibility issues and hours of maintenance… Nope. That’s why we created Bot Connector.

Bot Connector is a proxy between your bot and every channel you want to connect it to.

By using Bot Connector, you don’t have to go through each channels’ documentation, or to drown your code in “if elses”. No, you’ll benefit from a unified payload, no matter from which channel it comes from. And more importantly, you’ll be able to send your messages in a single format understood by every channel.

Currently, Bot Connector supports Kik, Slack and Messenger.

Your channel is not in the list? You can either let us know by creating an issue here, or you can code it and make a pull request to see it added in both the open-source github and our hosted version.

Which channel can I add ?

Every contribution is welcome. You can find the list of the integration currently supported here, along with some suggestions you could get started on! 

The best pick is a channel you frequently use in your daily life, or you want to know more of.

How is Bot Connector constructed?

Before we begin, let me explain how the Bot Connector works.

First, create a bot and save its endpoint. Then, add channels to your bot, each one being a connection between your bot and an app on Facebook, on Slack, or a bot on Kik. To do so, you need to retrieve some parameters on those platforms, like a token, a page ID, or a username. Each integration comes with its required payload.

Once your bot is set up, and your channels are created, your bot can receive and send messages.

When your bot receives a message

Bot Connector (illustrated by the glorious octopus here) receives the new message on the endpoint set at the creation of the channel.

It then parses the message in an universal format, saves it in a database, and forwards it to your bot, on the endpoint you’ve provided when you’ve created it.

When your bot sends a message

Here, the Bot Connector will save the message it has received from the bot in the database, format it in the native format of the channel, and send it back to the right channel.

It’s that simple.

Now, let’s see what Bot Connector has in its guts. The project is organised as follows:

Models

Models contains every MongoDB scheme of Bot Connector’s resources (bot, channel, message, etc). Each model has one (or more) serializers that define how they’re sent back to the user.

Routes

Every endpoint of the Bot Connector API is referenced here, split in files by resources.

Validators

Those are the function called to check if the parameters passed with the http requests are valid. If the parameters match those expected, the request continues to the controller. If not, an error (generally a 400), is returned.

As for the routes, validators are split in files according to the resource concerned.

Controllers

This is where the logic of Bot Connector is. It’s in those functions that the interaction with the MongoDB are made if you want to get, create, update or delete a resource. It also here that message processing is done, by the Message and Webhook controllers.

Once again, controllers are split by resources.

If you want to add an integration, you might be interested in the channel, webhook and message controllers.

Services

All integrations are contained here.

There’s also a template of functions you need to implement for your integration to work. We’ll go into more details later.

Utils

This folder contains various helpers and tools such as a logger, custom errors, etc.

To add a new integration, you don’t need to make changes in other files, but I strongly encourage you to have a look at the controllers (especially the channel, webhook and message one) to get a better view of what’s going on.

What do I need to do to add my new integration?

You can find the list of functions you either need or simply want to add to your integration in the Template.service.js file, under the services/ folder.

export default class ServiceTemplate {




  /* Call when the Connector is launched */

  static onLaunch = noop




  /* Check the parameters validity to create a Channel */

  static checkParamsValidity = noop




  /* Call when a channel is created */

  static onChannelCreate = noop




  /* Call when a channel is updated */

  static onChannelUpdate = noop




  /* Call when a channel is deleted */

  static onChannelDelete = noop




  /* Call when a message is received for security purpose */

  static checkSecurity = noop




  /* Call when a message is received, before the message is processed */

  static beforePipeline = noop




  /* Call before entering the pipeline, to build the options object */

  static extractOptions = noop




  /* Call to parse a message received from a channel to the Bot Connector format*/

  static parseChannelMessage = noop




  /* Call to format a message received by the bot to the concerned channel native format*/

  static formatMessage = noop




  /* Call to send a message to a bot */

  static sendMessage = noop

}

There are two categories of functions.

Primary functions

Those are mandatory! Your integration cannot work if you don’t implement them.

parseChannelMessage

Parses the incoming message of the channel to shape it in the standard format. The format is as follows:

{

type: ‘TYPE_OF_THE_MESSAGE’,

content: ‘CONTENT_OF_THE_MESSAGE’,

}

You can find more information on the message’s format here.

Parameters:

– conversation: the current conversation

– message: the content of the body with the message received from the channel

– opts: the options extracted by the extractOptions function

Returns:

– an array with the conversation, the parsed message and the options.

// Kik example

  static parseChannelMessage (conversation, message, opts) {




    const firtsMessage = message.messages[0]

    const msg = {

      attachment: {},

      channelType: 'kik',

    }




    switch (firtsMessage.type) {

    case 'text':

      msg.attachment = { type: 'text', value: firtsMessage.body }

      break

    case 'link':

      msg.attachment = { type: 'link', value: firtsMessage.url }

      break

    case 'picture':

      msg.attachment = { type: 'picture', value: firtsMessage.picUrl }

      break

    case 'video':

      msg.attachment = { type: 'video', value: firtsMessage.videoUrl }

      break

    default:

      msg.attachment = { type: 'text', value: 'we don\'t handle this type' }

      break

    }

    message = msg

    return [conversation, message, opts]

  }

formatMessage

Formats the message received from the bot to the channel native format. As for the parseChannelMessage function, you can find the details of the format you will receive here.

Parameters:

– conversation: the current conversation

– message: the content of the bot’s message

– opts: the options extracted by the extractOptions function

Returns: the message formatted

// Kik example

  static formatMessage (conversation, message, opts) {

    const reply = []

    let keyboards = null




    if (message.attachment.type === 'text') {

      reply.push({ type: message.attachment.type, chatId: opts.chatId, to: opts.senderId, body: message.attachment.content })




    } else if (message.attachment.type === 'picture') {

      reply.push({ type: message.attachment.type, chatId: opts.chatId, to: opts.senderId, picUrl: message.attachment.content })




    } else if (message.attachment.type === 'video') {

      reply.push({ type: message.attachment.type, chatId: opts.chatId, to: opts.senderId, videoUrl: message.attachment.content })




    } else if (message.attachment.type === 'quickReplies') {

      keyboards = [{ type: 'suggested' }]

      keyboards[0].responses = message.attachment.content.buttons.map(button => ({ type: 'text', body: button.title }))

      reply.push({ type: 'text', chatId: opts.chatId, to: opts.senderId, body: message.attachment.content.title, keyboards })




    } else if (message.attachment.type === 'card') {

      if (message.attachment.content.buttons && message.attachment.content.buttons.length) {

        keyboards = [{ type: 'suggested' }]

        keyboards[0].responses = message.attachment.content.buttons.map(button => ({ type: 'text', body: button.title }))

      }

      if (message.attachment.content.title) {

        reply.push({ type: 'text', chatId: opts.chatId, to: opts.senderId, body: message.attachment.content.title })

      }

      if (message.attachment.content.imageUrl) {

        reply.push({ type: 'picture', chatId: opts.chatId, to: opts.senderId, picUrl: message.attachment.content.imageUrl })

      }

      reply[reply.length - 1].keyboards = keyboards

    } else {

      reply.push({ type: 'text', chatId: opts.chatId, to: opts.senderId, body: 'wrong parameter' })

    }




    return reply

  }

extractOptions

Extracts useful information from the incoming request received from a channel when a new message is posted.

Parameters:

– req: the request received from the channel

Returns: an object with at least the chatId and the senderId keys. You can extract more depending on your needs.

// Slack example

  static extractOptions (req) {

    const { body } = req

    return {

      chatId: body.messages[0].chatId,

      senderId: body.messages[0].participants[0],

    }

  }

sendMessage

Sends the message received from the bot and formatted by Bot Connector to the corresponding channel.

Parameters:

– conversation: the current conversation

– messages: the array of messages to send

Returns: nothing

// Kik example

  static async sendMessage (conversation, messages) {

    for (const message of messages) {

      await agent('POST', 'https://api.kik.com/v1/message')

        .auth(conversation.channel.userName, conversation.channel.apiKey)

        .send({ messages: [message] })

    }

  }

Secondary functions

Those functions aren’t mandatory for your integration to work, but might be useful. They are called at different times in a channel’s lifetime, and do what you want at a specific time.

beforePipeline

This function is called when a message is received from a channel, before processing the message. Most of the time, you won’t have to handle specific logic here, but you can send back a 200 to the channel , meaning you have received the new message.

Parameters:

– res: the response that will be sent back to the channel

Returns: nothing

// Messenger example

  static async beforePipeline (res) {

    res.status(200).send()

  }

checkSecurity

Checks if the message is valid and not coming from an external service trying to push fake messages.

Parameters:

– req: the request received from the channel

– res: the response that will be returned

– channel: the current channel

Returns: whether or not the message is received from a valid sender.

// Kik example

  static checkSecurity (req, channel) {

    return ('https://'.concat(req.headers.host) === channel.webhook && req.headers['x-kik-username'] === channel.userName)

  }

checkParamsValidity

This function is called before a channel is created to check that the parameters specific to this channel type are present and valid.

Parameters:

– channel: the current channel

Returns: true if everything went well / throw a ValidationError if a parameter is invalid / missing

// Kik example

static checkParamsValidity (channel) {

    const { userName, apiKey, webhook } = channel




    if (!apiKey) { throw new ValidationError('apiKey', 'missing') }

    if (!webhook) { throw new ValidationError('webhook', 'missing') }

    if (!userName) { throw new ValidationError('userName', 'missing') }




    return true

  }

onLaunch

Called when Bot Connector is launched. This function isn’t required, but if your channel needs a web socket connection for example, it will be opened in this function.

// Slack example

  static onLaunch () {

    return new Promise(resolve => {

      Channel

        .find({ type: 'slack' })

        .then(channels => {

          channels.forEach(c => SlackService.onChannelCreate(c))

          return resolve()

        })

        .catch(err => {

          Logger.error(`Error while handling slack socket: ${err}`)

          return resolve()

        })

    })

  }


onChannelCreate

Called when the channel is created. Can be useful to subscribe to webhooks.

Parameters:

– channel: the channel that has just been created

Returns: a promise

// Kik example

static onChannelCreate (channel) {

  const data = {

    webhook: `${channel.webhook}/webhook/${channel._id}`,

      features: {

        receiveReadReceipts: false,

        receiveIsTyping: false,

        manuallySendReadReceipts: false,

        receiveDeliveryReceipts: false,

      },

    }




    return new Promise((resolve, reject) => {

      request.post('https://api.kik.com/v1/config')

        .auth(channel.userName, channel.apiKey)

        .send(data)

        .end(err => {

          if (!err) { return resolve() }

          return reject({

            error: 'Error while subscribing bot to Kik server',

            status: 502,

          })

        })

    })

  }

onChannelUpdate

Called when the channel is updated. In most cases, you want to do the same logic as in the onChannelCreate function (subscribe to a webhook, open a socket connection, …).

Parameters:

– channel: the channel that has just been updated

// Slack example

  static async onChannelUpdate (channel) {

    SlackService.onChannelDelete(channel)

    await SlackService.onChannelCreate(channel)

  }

onChannelDelete

This function is called when the channel is deleted. Once again, you will not need this function. If we take again the websocket example, in this function you would have to close the socket connection.

Parameters:

– channel: the channel about to be deleted

Returns: nothing

// Slack example

  static onChannelDelete (channel) {

    const rtm = SlackService.allRtm.get(channel._id.toString())

    if (rtm) {

      rtm.disconnect()

      SlackService.allRtm.delete(channel._id.toString())

    }

  }

Guidelines

You can find the full contributing guidelines here

If you want to add an integration, please create an issue on the Bot Connector project on GitHub, so everyone knows who’s working on what, and to avoid duplicated integrations. It’s also the best channel to ask your questions or suggest integrations!

And please keep in mind that no matter what, we have cool t-shirts and awesome stickers waiting to be shipped to y’all. Cheers!

Want to build your own conversational bot? Get started with Recast.AI !

Subscribe to our newsletter


There are currently no comments.