Working with Chat Bots

Welcome to the Facebook Instant Games SDK Tutorials. This series of tutorials covers many aspects of using the Facebook Instant Games SDK.

In this tutorial we will take a look at working with chat bots using the Facebook Instant Games SDK.

What are Messenger Bots?

From a Facebook Instant Games perspective a Messenger bot is some back-end code that can message a subscribed Messenger user. The main purpose of bots is to help retain users by reminding them that your game is there. Games can for example send reminders to players about daily rewards, free items, the game status of friends that also play the game and so on.

In order to use bots you need to create an app page for your game then associate it with your game in the Instant Games section of the Facebook developer dashboard. You also need to add the Messenger product to your Instant Game in the developer dashboard and set up a Webhook to enable communication between Messenger and your bot back-end.

I’m not going to cover setting up a bot from scratch in this tutorial as it is not the purpose of this tutorial, but you can get more information here.

Note that once your bot is complete you will need to submit it for review.

Bot Subscriptions

By default players are not subscribed to receive notifications from your bot, you need to get their permission in order to send messages to them. Lets take a quick look at code that will ask the player to subscribe to receive notifications from your bot:
[sourcecode language=”js”]
FBInstant.player.canSubscribeBotAsync().then(function(yes)
{
if (yes)
{
FBInstant.player.subscribeBotAsync().then(function()
{
// User subscribed to the bot
}
).catch(function (e) {
// User did not subscribe to the bot or something went wrong
});
}
});
[/sourcecode]
Note that the user will be shown a pop-up dialog that expressly asks for their permission which they can cancel.
When the player subscribes to your bot, a signal is sent to your back-end via a webhook that is associated with your apps page, you will need to detect this on the back-end and carry out some basic tasks such as adding the player to your database.

Passing Data To The Bot

You can send game state data to your bot back-end from within the game using FBInstant.setSessionData(data), passing in an object that will be sent:

[sourcecode language=”js”]
data = {
current_level: 10,
score: 1234,
plays: 5
};
FBInstant.setSessionData(data);
[/sourcecode]

The Basics of a Bot Back-end

I wrote a free open source Facebook Instant Games multi-game bot server using node.js, with express and Redis for back-end storage which you can use. In this section we will look at the basics of this bot code and figure out what it is doing. Lets take a look at the most important part of the bot which handles messages from the webhook:
[sourcecode language=”js”]
var express = require("express");
var app = express();
var bodyParser = require("body-parser");
var cors = require("cors");

app.set("port", (process.env.PORT || 8000));
app.use(bodyParser.json());
app.use(cors());

app.listen(app.get("port"), function()
{
});

app.get("/bot", function(request, response)
{
if (request.query["hub.mode"] === "subscribe" && request.query["hub.verify_token"] === pages.GetVertifyToken())
{
response.status(200).send(request.query["hub.challenge"]);
}
else
{
console.log("Failed validation. Make sure the validation tokens match.");
response.sendStatus(403);
}
});
app.post("/bot", function(request, response)
{
var data = request.body;
if (data.object === "page" && data.entry !== undefined)
{
data.entry.forEach(function(entry)
{
if (entry.messaging !== undefined)
{
var page_id = entry.id;
// Iterate over each messaging event
entry.messaging.forEach(function(event)
{
if (event.message)
{
HandleMessage(event);
}
else if (event.game_play)
{
HandleGameplay(event, pages.GetGame(page_id));
}
else
{
console.log("Webhook received unknown event: ", event);
}
});
}
});
}
response.sendStatus(200);
});

function HandleMessage(event)
{
// We ignore user messages sent by the user
}

function HandleGameplay(event, game)
{
var sender_id = event.sender.id;
var player_id = event.game_play.player_id;
var context_id = event.game_play.context_id;
var payload = event.game_play.payload;
AddPlayer(sender_id, player_id, context_id, game, payload);
}
[/sourcecode]

In the above code we start a server running and handle a single GET and POST request sent to the bot end point. The GET request is a subscription request from the webhook which is sent when you first set up your webhook.

The POST request is a webhook that is sent to the back-end when a subscribed user exits your game. It is at this point where we collect information about the player. The event that is sent contains information about the player such as their ID, the context ID they are playing in and a payload. The payload is data that you have set as the session data with the call to FBInstant.setSessionData(data).

The call to AddPlayer() simply adds the players credentials to back-end storage so the player can be messaged at some point in the future.

I’m not going to go into much more depth regarding the bot code than this as that is not the point of this article. However, here is a little more info regarding the free open source bot that I have provided. The bot consists of a number of files:

  • bot.js – The main bot which accepts requests and creates new players
  • bot_none_cluster.js – None cluster version of bot.js (can be used with pm2 -i option)
  • crawler – Crawls through the database checking for players that need to be messaged and messages them, also removes players that do not respond from the database
  • messaging.js – Sends messages out to players
  • pages.js – Stores page data

The code itself is very simple in nature but very fast and not difficult to figure out. I use a version of this bot to power all 14 of my own Facebook Instant Games and it runs at 1-2% CPU on a $5 per month Digital Ocean droplet.

To run the bot you need to run both bot.js and crawler.js, I suggest using a process manager such as PM2 to run them both.

Bot Limitations

Its important to remember that once the player exits your game, your bot back-end can only message the player up to 5 times within 10 days of their last play. If you attempt to send more than 5 messages within that 10 day period or that 10 day period expires then your bot will be blocked from sending that user any more messages. It will also remain blocked until the user chooses to come back and play your game, at which point the counter and timer are reset.

Bot Issues

Sometimes and for no apparent reason Bots can just stop working, this has happened to me many times. I have found that the only solution to fix the issue is to unsubscribe the webhook and resubscribe then regenerate the page access token and use the new token in my back-end.

Passing Data from Bot to Game

Data can be passed back to the game from the bot by attaching a payload to the button in message attachment data that is sent by the bot to the game when the game is launched via a bot play message. When hte game is launched from a bot message the payload data will be available via FBInstant.getEntryPointData(). Lets take a look at a quick example:

[sourcecode language=”js”]
// Your data to send to the game which you can collect via getEntryPointData()
var dataToSend = {
level: 1,
score: 50
};

// sender_id is page-scoped ID of the message recipient
var messageData =
{
recipient: {
id: sender_id
},
message: {
attachment: {
type: "template",
payload: {
template_type: "generic",
elements: [
{
title: "Come get some yumyums",
subtitle: "They are hot",
image_url: "link to an image that paints a thousand words",
buttons: [
{
type: "game_play",
title: "Play",
payload: JSON.stringify({ myReplayData: dataToSend })
}
]
}
]
}
}
}
};

// pat is page access token
function CallSendAPI(messageData, pat)
{
var graphApiUrl = "https://graph.facebook.com/me/messages?access_token=" + pat;
request({
url: graphApiUrl,
method: "POST",
json: true,
body: messageData
}, function (error, response, body){
console.log("Send API returned", "error", error, "status code", response.statusCode, "body", body);
});
}
[/sourcecode]

Leave a Reply