The COVID-19 pandemic is still ongoing and it has brought along with itself a plethora of data, which is being made available by various APIs. In this article, we’d like to take advantage of one of them, and at the same time teach you how to build a simple Facebook Messenger Bot in the process.
What we’ll build
We’re going to build a Messenger bot, where users can ask about statistics for countries. So, if the user wants to know about new cases for today in the US, they can just ask the bot “USA”, and the bot will reply with some numbers. We’ll also make the bot suggest next questions (using “quick replies”), so the user can get even more info for the country.
We’ll take this step by step, and by the end of the tutorial, you should be able to quickly whip up a Messenger bot for almost any purpose you need.
Requirements
We’ll be building our bot with Ruby. It doesn’t matter much what version of Ruby you use, but for what it’s worth, we’re using version 2.7.1. To install it, follow the instructions on the official website. Depending on your platform, you might already have Ruby installed. Other than that, we’ll be assuming you already have some basic knowledge of Ruby, but if you’ve worked with other programming languages, it shouldn’t be too difficult to pick up on. You’ll also need to install bundler
. To do this simply run gem install bundler
in your terminal. Apart from that, we’ll be using:
- Facebook: you’ll need a Facebook account to be able to use their developer platform,
- Sinatra: we’re going to use this simple web framework, as our bot is not extremely complex. You can use any web framework, including Ruby on Rails for this,
- Excon: this is a simple yet powerful Ruby library to make web requests, which we’ll need to use the APIs our bot will access,
- ngrok: a nifty tool that allows us to expose ports in our machine to the internet. You’ll see why in a bit.
Make sure you set up ngrok according to their instructions. We’re going to need this tool later. You don’t need to sign up or pay for it, as they have a free plan which is more than enough for our needs.
Facebook Page and App
All Messenger bots need to be associated with a Facebook Page. You can’t (as of writing) write an “independent” bot. Because of this, our first step is to create a page. If you already have a page you’re not using, you could also use it to test out your bot! If that’s the case, you can skip this step and go straight to creating the Facebook App. To create a Facebook page, log in to Facebook and on the top right section click the Create
button, then click on Page
. Depending on the type of page you’re creating, choose either “Business” or “Community or Public Figure”. We chose the latter, but if you’re just creating a test page, it doesn’t matter much.
Name your page accordingly, and choose a category. Follow the next steps (which you can skip, for now, you can always choose images later), and you should be presented with your new page. We’re now going to create the Facebook App. This is going to allow us to act as our page in messenger (among other things). For this, head to developers.facebook.com and click on “My Apps” on the top right. From there, click on “Add a New App”.
Select that you want to “Manage Business Interactions”, then name your app (in our case we named it COVIDBot). Add a contact email and click the “Create App ID” button to finish. You should now be presented with your App Dashboard. Here, you can scroll down to find the Messenger card. Click the “Set Up” button to get started.
You should then be greeted with the Messenger Dashboard for your app. Here, click on “Add or Remove Pages”, where you’ll associate your Page with your App.
You’ll be asked to continue using your user, which you need to say yes to, then select your page from the list, then make sure the “Manage and Access Page conversations in Messenger” is on. Click on “Done”.
There’s one last thing we need to do here, but first, we need to get started on our bot!
The Bot
Make a new directory called covid-bot
and using your terminal, run bundle init
. This initializes a Gemfile in your directory. Now, using your preferred editor, add some gems to the Gemfile:
gem "excon" gem "sinatra" gem "sinatra-contrib"
Here, excon is our HTTP library (which we’ll use to fetch data from our APIs, and also for sending messages in Messenger), and Sinatra is our web framework. sinatra-contrib
allows us to get auto-reloading of our code during development (among other things, which we won’t use/cover in this article). Run bundle install
to install our dependencies.
Create a new file called app.rb
and add this to it:
require 'sinatra' require 'sinatra/reloader' if development? VERIFY_TOKEN = 'my-verification-token' before do content_type :json end get '/' do mode = params['hub.mode'] verify_token = params['hub.verify_token'] challenge = params['hub.challenge'] if mode == 'subscribe' && verify_token == VERIFY_TOKEN challenge else halt 401 end end
What we’re doing here is requiring Sinatra and the automatic reloader. We then define our “verification token”. This verification token is something we specify when creating a webhook in our Facebook App. When we create it, Facebook will make a request to the endpoint we specify and will send this token so we can verify it’s actually Facebook making the request. For this article, we’re setting it to something simple, but in a real-world scenario, you’d use something more unique (like a UUID, for instance). Also, we recommend not hard-coding this into your code, but rather use some sort of secrets storage or just set it as an environment variable.
Next, we specify a before
filter where we tell Sinatra all our responses will be JSON. This way Sinatra sets the correct headers when responding to requests (by default it uses HTML responses). Finally, we define our GET index method. This is the endpoint Facebook will call when creating the webhook, thus, we need to verify the token is correct and that the mode
is subscribe
. If these are correct, we’re supposed to reply with the challenge that is sent by Facebook. You can read more about this process here.
Setup the Webhook
Before setting up the webhook on our Facebook Developer Dashboard, we need to start our server and our ngrok tunnel (so Facebook can reach our local development server). First, in your development folder, run the command ruby app.rb
. This will start your local server, and you should be greeted with something like this:
On a different terminal window, run ngrok http 4567
. This will start a local tunnel into your machine on port 4567 (which is the port Sinatra should be running in). You’ll see something like this on your terminal:
Notice and copy the https
endpoint. You’ll need to use this to set up the webhook. Now go to the Facebook Developer Dashboard and scroll down to find a section called “Webhooks”. Click the “Add Callback URL” button.
In the window that opens, enter the https
endpoint you got from ngrok and the verification token you chose (in our case my-verification-token
). Now click “Verify and Save”. You should notice a new request was made to your Sinatra app, and the verification process should succeed. One last step before continuing is to subscribe to message events. We do this by clicking the “Edit” button next to our Page in the webhooks section, selecting the messages event, and saving.
We’re now ready to use our bot!
Generate a Token and Send Messages
Since you’re already in the dashboard, go ahead and look for the section “Access Tokens”. You should see your Facebook Page in the list. Next to it, click the button “Generate Token”.
Click the “I Understand” checkbox, and copy the token someplace safe, for now.
Insert this code before your before
block:
require 'excon' PAGE_TOKEN = 'INSERT_YOUR_TOKEN_HERE' def send_api(recipient_id, message) Excon.post( "https://graph.facebook.com/v2.6/me/messages?access_token=#{PAGE_TOKEN}", headers: { 'Content-Type' => 'application/json' }, body: { recipient: { id: recipient_id }, message: message }.to_json ) end
This snippet requires the HTTP library, then defines your newly created token as a constant (again, we recommend storing this differently for production applications), and defines a method to send Messenger messages via the Facebook API. This method should be fairly straightforward, as we simply POST a structured JSON payload to their API, specifying the who the recipient is (the ID we get when processing a new message), and a message. This message can contain text and other things, as you’ll see later.
Now, define a POST handler in our bot. Facebook POSTs the messages to our webhook, so we’ll need to process that accordingly:
post '/' do json = JSON.parse(request.body.read) halt 400 unless json['object'] == 'page' json['entry'].each do |entry| body = entry['messaging'].first sender_id = body['sender']['id'] send_api( sender_id, { text: 'Hi! This is an example message from the bot!' } ) end halt 200 end
First, we parse the JSON payload into a Ruby hash. Then, if the payload isn’t something we support yet, we stop with a 400 status code. If all is fine, we process the entries in the payload (there can be more than one). For each, we’ll grab the sender’s id, and for now, we’ll just reply with a simple message, using our send_api
method. Notice the payload we’re sending is a hash and not just a string. You’ll understand later why. Finally, we should respond with status 200, otherwise, the webhook will fail to work.
Go ahead and try this out. You should be able to go to Facebook Messenger and find your page there. Then, just type whatever and send it. You should see a message from your bot!
Add COVID statistics
Now, let’s add some liveliness to our bot. For this, we’ll use a COVID-19 API you can find on RapidAPI. You’ll need to sign up for an account. This particular API we’re using is free to use, but you can find many APIs which provide a lot of functionality, and most provide a free usage tier, perfect for development. On the API page, click the “Subscribe to Test” button, and select the Basic plan. You can now go to the “Endpoints” tab and copy your API Key (which you’ll find on the right side). Now, add this to your app.rb
file:
RAPIDAPI_KEY = 'YOUR_RAPID_API_KEY' def get_countries response = Excon.get( 'https://covid-193.p.rapidapi.com/countries', headers: { 'x-rapidapi-key' => RAPIDAPI_KEY } ) JSON.parse(response.body)['response'].map(&:downcase) end def get_statistics(country) response = Excon.get( 'https://covid-193.p.rapidapi.com/statistics?country=' + country, headers: { 'x-rapidapi-key' => RAPIDAPI_KEY } ) JSON.parse(response.body)['response'].first end COUNTRIES = get_countries
First, we’ll store our RapidAPI key in a constant (beware again of hard-coding this in a production environment). Then, we’ll define two methods. The first one, get_countries
we’ll use to get a list of countries the API has data on. In the last line of the snippet, you’ll see we’re storing this in a constant. This way, the method gets called when booting the server. The method itself is pretty simple: we just parse the response from JSON and make sure we store the names of the countries downcased, for easier comparison.
The second method is used to get statistics for a specific country. Again, a pretty straightforward method where we just use the statistics endpoint of the API to get the data. You can play around with both these endpoints on RapidAPI’s website, so you can familiarize yourself with the structure of the responses. Next, we’ll need to modify our post '/'
block:
post '/' do json = JSON.parse(request.body.read) halt 400 unless json['object'] == 'page' json['entry'].each do |entry| body = entry['messaging'].first sender_id = body['sender']['id'] if COUNTRIES.include?(body['message']['text'].downcase) # Get statistics stats = get_statistics(body['message']['text'].downcase) if stats send_api( sender_id, { text: "As of #{stats['day']}, #{stats['country']} has #{stats['cases']['total']} cases, #{stats['deaths']['total']} deaths, and has run #{stats['tests']['total']} tests." } ) else send_api( sender_id, { text: "Couldn't find any statistics for #{body['message']['text']}" } ) end else send_api( sender_id, { text: "Sorry, I can't find that country." } ) end end halt 200 end
There’s a lot more going on now. But it boils down to this: we try to match the message sent by the user to a country on our list. If found, we’ll send some statistics about it. If we didn’t get any statistics from the endpoint, then we’ll say we couldn’t find anything for that country. If we simply can’t find the country on the list, we’ll tell the user that.
Give your bot a try now. You should be able to send a country and get some statistics.
Interactive Messages
You’ve surely seen these in some bots, where they give you choices after responding. These are pretty handy to increase engagement and the usability of your bot. Let’s add that! Add this method among the others in your code:
def quick_replies(country) [ { content_type: 'text', title: 'New Cases', payload: "#{country}:new_cases" }, { content_type: 'text', title: 'New Deaths', payload: "#{country}:new_deaths" } ] end
We’ll use this in a bit. What this will build is an extra row of options for the user, which when clicked will send a special payload to our bot, which we can process accordingly. For example, if the user searches for “USA”, then we’ll send some statistics about the US, plus two extra buttons, that, when clicked, will send a payload like USA:new_cases
to our bot. We can process this and send another message. Modify your post '/'
block to look like this now:
post '/' do json = JSON.parse(request.body.read) halt 400 unless json['object'] == 'page' json['entry'].each do |entry| body = entry['messaging'].first sender_id = body['sender']['id'] if body['message']['quick_reply'] country, stat = body['message']['quick_reply']['payload'].split(':') stats = get_statistics(country) text = if stat == 'new_cases' "#{stats['cases']['new'] || 0} new cases." else "#{stats['deaths']['new'] || 0} new deaths." end send_api( sender_id, { text: "On #{stats['day']}, #{stats['country']} had #{text}", quick_replies: quick_replies(stats['country']) } ) elsif COUNTRIES.include?(body['message']['text'].downcase) stats = get_statistics(body['message']['text'].downcase) if stats send_api( sender_id, { text: "As of #{stats['day']}, #{stats['country']} has #{stats['cases']['total']} cases, #{stats['deaths']['total']} deaths, and has run #{stats['tests']['total']} tests.", quick_replies: quick_replies(stats['country']) } ) else send_api( sender_id, { text: "Couldn't find any statistics for #{body['message']['text']}" } ) end else send_api( sender_id, { text: "Sorry, I can't find that country." } ) end end halt 200 end
That’s a lot more than last time. But most of it is unchanged. The first few lines will check if the message we got is a message with a payload from a quick reply (meaning a user clicked one of the buttons). If it is, we’ll deconstruct it to get the country and the statistic they want, get statistics again and generate a message depending on the requested type. Then we’ll send a new message, including the quick replies buttons, in case they’d like to click the other one (quick replies disappear after clicking them). In case we get a regular message, then we’ll do the same as before, just adding the quick replies to our response. Try your bot again, you should see the quick replies at the bottom of the responses:
Conclusion
We hope this tutorial helped give you a quick introduction to how to build Messenger bots. The basics are not overly complicated, but you can of course build much more intricate bots using everything they offer. We recommend looking at their documentation for more info. You can, for example, send attachments (think, a number of cases chart), or even receive attachments and process them (for example a bookkeeping bot that processes receipts).
You could leverage some of the many APIs available on RapidAPI to build extremely powerful bots. Continuing on the last example, you can use OCR processing APIs to process handwritten notes, simply by sending them to your bot via Messenger.
Leave a Reply