β οΈ Hooray! Rubotnik is now a gem that can generate a project for you and serve it from localhost or Heroku. There's been some API changes since it has been a boilerplate. If you are looking for an old README and rubotnik-boilerplate's code you can find it here.
Rubotnik
Tiny "bot-end" Ruby framework with intuitive DSL to quickly build, test and deploy a Messenger chatbot
Only minimal Ruby knowledge is required. Perfect for pet projects, classrooms and building bot MVPs from scratch while having a complete freedom over what your bot can do (ChatFuel is great, but what if you actually want to code?). Probably not the best solution for commercial projects at the momentβRubotnik is yet to prove its worth in production.
This is how you greet your users with Rubotnik:
Rubotnik.route :message do
bind 'hi', 'hello', 'bonjour' do
say 'Hello from your new bot!'
end
end
Rubotnik is zero-configuration, you can have a conversation with your bot on Messenger in under 10 minutes (and most of that time you'll spend on Facebook and Facebook for Developers, creating a page and an "app" for the bot).
Rubotnik is built on top of an excellent facebook-messenger gem by Johannes Gorset that does all the heavy-lifting to actually interact with the Messenger Platform API. While facebook-messenger
implements a client, rubotnik
offers you a way to reason about your bot design without dozens of nested if
and case
statements. Rubotnik comes with a bare-bones architecture that ensures multiple connected users are served without delays or overlaps.
What the heck is "bot-end"?
Exactly as with modern front-end, "bot-end" is a separate web application that can talk with your back-end through a (REST) API. Project generated with Rubotnik uses Puma as web server, contains its own config.ru
and can be deployed to Heroku in a matter of minutes. And you can still use Heroku's free account, as "sleep time" is not a deciding factor in bot interactions.
Rubotnik currently can not be integrated directly inside the Rails project (although thoughts on that are welcome) and does not have a database adapter of its own on purpose, to keep the boilerplate to the very minimum and extract conversational logic to a separate layer.
In other words:
Rubotnik is perfect to consume existing APIs from a chatbot interface
NB: Rubotnik comes with httparty library to make HTTP requests, but you can use any other tool by including it in the project's Gemfile.
Installation
It is recommended to install Heroku CLI, Ngrok and Bundler before you start.
$ gem install rubotnik
And then create your bot with:
$ rubotnik new PROJECT_NAME
Now you can cd
to PROJECT_NAME
, open it in your favorite editing and start hacking!
To start playing with your bot directly from Messenger (after you've created an app in Facebook Developer Console):
$ bundle install
$ heroku local
And in a new Terminal tab:
$ ngrok http 5000
Usage
A project generated with rubotnik new
will have a following simple structure:
.
βββ Gemfile # contains a single dependency on "rubotnik" gem
βββ Procfile # ready for Heroku
βββ bot
βΒ Β βββ bot.rb # <-- YOUR STARTING POINT
βΒ Β βββ commands
βΒ Β βΒ Β βββ commands.rb # define any commands as methods here
βΒ Β βΒ Β βββ location.rb # or here (contains example for handling user location)
βΒ Β βΒ Β βββ ui_examples.rb # or here (contains examples for UI elements)
βΒ Β βββ profile.rb # Welcome page, "Get Started" button and a menu for your bot
βββ config
βΒ Β βββ puma.rb # Puma settings
βββ config.ru # it's a Rack app, after all!
All the magic happens inside Rubotnik.route :message
and Rubotnik.route :postback
blocks, this is where you can use Rubotnik's DSL to bind user messages to "commands" that your bot is going to execute.
A "command" is just a plain Ruby method inside the Commands
module that has access to message
(or postback
) and user
objects. This is how you match incoming message to a command:
# Will match any of the words and their variations
bind "hello", "hi", "bonjour", "ΠΏΡΠΈΠ²Π΅Ρ", to: :my_method_for_greeting
# Will match only if all words are present (ignoring order)
bind "what", "time", "is", all: true, to: :tell_time
# Same logic for postbacks
bind "ACTION_BUTTON", to: :action_for_button
bind
can also take a block for a simple response that does not merit its own method:
bind "damn" do
say "Watch your language, human!"
end
If none of the commands are matched, a default
block will be invoked
# Invoked if none of the commands recognized. Has to come last, after all binds
default do
say "Come again?"
end
Setup
-
Login to Facebook For Developers. In the top right corner, select My apps > Add a new app.
-
Select a product "Messenger"
-
Under Token Generation in Products > Messenger > Settings select a page for your bot, or create a new one. Copy the Page Access Token and insert it in
.env
in the Rubotnik-generated project underACESS_TOKEN
-
While still in
.env
, come up with any string forVERIFY_TOKEN
variable (or leave the defaultverify_me
) -
From the Terminal, while being in the same folder as your project, open a new tab and run
heroku local
. It will load environment variables from.env
and start Puma server on port 5000. -
Open another tab und run
ngrok http 5000
that will expose port 5000 to the Internet. (note, depending on your Ngrok installation, you may want to specify path tongrok
executable when running it). -
Copy the Forwarding address from the ngrok tab. It should start with https://
-
In the Facebook dashboard, while still under Products > Messenger > Settings, click Setup Webhooks. Under Subscription Fields select "messages" and "messaging_postbacks". Under Callback URL, paste your ngrok secure forwarding address and postpend it with
/webhook
(that's important!). Put your verification token under Verify Token -
Click Verify and Save. Once the modal closes, you will see that under Webhooks section in the dashboard you can now "Select a page to subscribe your webhook to the page events". Select your page and hit "Subscribe".
-
You're done! Now you can find your bot in Messenger under your page's name and start talking to it. Check with your generated project to see what commands are included for demonstration purposes. Start tweaking the bot to your liking!
Debugging your bot
Debugging bots under Facebook Messenger Platorfm is not the most pleasant experience β if your client can not handle the POST
request to your webhook from the API that contains a message relayed from user β Facebook will try resending the message again and again, before finally disabling your bot if it still can not return a 200 response. In order to save you from that trouble, Rubotnik sets $DEBUG
environment variable to "true" while you're on localhost.
While in this mode, every StandardError
exception raised by your bot's code will be forwarded as a bot response to the chat dialogue, ensuring the conversation flow is never broken. As a developer, you will be able to see what is wrong with your bot while talking it. I find it a very natural experience.
Note that Rubotnik does not reload the server on code change. Every time you want to add a new feature or modify an existing oneβyou will have to relaunch heroku local
after code save.
Currently, Rubotnik has quite a verbose logging to stdout
that will help you make sense of what's going on.
Developing with Rubotnik
Messages, postbacks and menus
Your starting point is bot.rb
file that serves your bot, enables its persistent menu and a greeting screen, and provides top-level routing for messages and postbacks.
Received messages and postbacks are instances of Facebook::Messenger::Incoming::Message
and Facebook::Messenger::Incoming::Postback
objects of facebook-messenger gem and have all the properties defined in its README.
message.id # => 'mid.1457764197618:41d102a3e1ae206a38'
message.sender # => { 'id' => '1008372609250235' }
message.seq # => 73
message.sent_at # => 2016-04-22 21:30:36 +0200
message.text # => 'Hello, bot!'
message.attachments # => [ { 'type' => 'image', 'payload' => { 'url' => 'https://www.example.com/1.jpg' } } ]
profile.rb
contains constants that provide hash structures (mimicking JSON from Facebook docs) for payloads necessary to enable the "Start Button", "Welcome Screen" and "Persistent Menu" of your bot. Modify them to your liking. Note that it's better not to change them very often after enabling them, as Facebook caches interface elements.
You can enable them all or some of them by putting # Rubotnik.set_profile(Profile::START_BUTTON, Profile::START_GREETING, Profile::SIMPLE_MENU)
in your code.
Users
Every connected user will be given it's own User
object that is stored in the hash in memory, while your bot is running. You can reference user
or @user
(both will work) from anywhere inside your code.
user.id
will give you an unique identifier for connected user (assigned by Facebook), and user.session
is a hash that can be used for storing any user-related data during the session. For instance, you can design a thread of conversation that will ask user for some information, keep bits of that information as different keys under user.session
hash and make a POST call with httparty (included in your project) or any other tool to your application back-end to persist the data.
Conversation threads
One of the most painful points in bot development is conversation branching. Rubotnik tries to make that a little bit more intuitive by allowing you to chain "command" methods together. Here's an example that you will find in the generated project.
In bot.rb
:
Rubotnik.route :message do
bind 'how', 'do', 'you', all: true, to: :start_conversation, reply_with: {
text: "I'm doing fine! You?",
quick_replies: [['Good!', 'OK'], ['Not so well', 'NOT_OK']]
}
end
Any message from user that has any combination of words "how", "do" and "you" (e.g. "How are you doing?", "How do you do?") will trigger a response defined under reply_with
. text
is a text of the response and quick_replies
is an optional array of quick replies (maximum 11) that will be attached to your bot's message like this:
Any further user action will now be handled by start_conversation
command under Commands module. If the user chooses to ignore "quick replies" hints and just types something in the message box, that will be a regular message.text
, if he clicks on one of supplied quick replies, that will result in a received message
with two properties: message.text # => "Good!"
and message.quick_reply # => "OK"
. Now you can handle it in start_conversation
:
def start_conversation
message.typing_on # simulate "typing"
case message.quick_reply # switch on quick_reply property
when 'OK'
say "Glad you're doing well!"
# end conversation, further input will be handled by top level bindings
# defined under Rubotnik.route :message
stop_thread
when 'NOT_OK'
say "Too bad. What happened?"
next_command :appear_nice # pass the control further down the thread
else
say "π€"
# it's always a good idea to have an else, quick replies don't
# prevent user from typing any message in the dialogue
stop_thread
end
message.typing_off
end
def appear_nice
message.typing_on
case message.text
when /job/i then say "We've all been there"
when /family/i then say "That's just life"
else
say "It shall pass"
end
message.typing_off
stop_thread # end conversation
end
This is the breakdown of the sequence of events:
- User sends "How do you do" =>
Rubotnik.route :message
binding tostart_conversation
command is invoked with areply_with
option =>- Your bot replies with "I'm doing fine!" and attaches quick replies "Good!" and "Not so well" to the message =>
- User selects "Good!" =>
- A user message with
message.text
"Good!" andmessage.quick_reply
"OK" is handled bystart_conversation
method. You can switch on any or both properties => - Method either passes control to a next method with
next_command
or stops interaction (returning user to the "top level" of conversation) withstop_thread
Helpers
Rubotnik gives you a number of helper methods (defined here:
say
:say "Hi"
orsay "Choose a pill", quick_replies: %w[Red Blue]
will immediately send a message to the user.quick_replies
accepts either an array of strings (thenpayload
option is automatically set to text in ALL CAPS), an array of string arrays (['Reply Text', 'REPLY_PAYLOAD']
), or an array of hashes that follow Facebook's format:
[
{
content_type: "text",
title: "<BUTTON_TEXT>",
image_url: "http://example.com/img/red.png",
payload: "<STRING_SENT_TO_WEBHOOK>"
},
{...}
]
-
show
takes an instance ofUI
element and sends it to the connected user. -
text_message?
allows you to check if the message received from the user contains text (and isn't a GIF, a sticker or anything else). Useful for implementing sanity checks. -
message_contains_location?
checks if the user has shared a location with your bot. Then you can access its coordinates withmessage.attachments
. -
get_user_info(*fields)
takes a list of fields good for Graph API User and makes a call to the Graph referencing connected user's id and requesting specified fields. Returns a hash with user data. Keys are symbols.
get_user_info(:first_name, :last_name) # => { first_name: "John", last_name: "Doe" }
next_command :command_name
andstop_thread
are used to control conversation flow.
Supported UI elements
Button Template
UI::FBButtonTemplate
takes two arguments: string for the text message and an array of hashes for buttons. See types of buttons available in Messenger Platform docs.
TEXT = "Look, I'm a message and I have some buttons attached!"
BUTTONS = [
{
type: :web_url,
url: 'https://medium.com/@progapanda',
title: "Andy's Medium"
},
{
type: :postback,
payload: 'BUTTON_TEMPLATE_ACTION',
title: 'Useful Button'
}
]
# create template object and send it to connected user
template = UI::FBButtonTemplate.new(TEXT, BUTTONS)
show(template)
If you have a button of type 'postback', you will be responsible to implementing the trigger for that action under Rubotnik.route :postback
as bind 'BUTTON_TEMPLATE_ACTION', to: :do_smth_on_button_click
.
Generic Template
Generic template is a way to send the user a carousel of items, each consisting of an image, a title, a description and up to 3 action buttons. Each card can be made clickable and link to a website. Constructing Generic Template involves building a long nested JSON (refer to Facebook docs to see what keys are available) and Rubotnik tries to abstract it at least a little bit. You only need to build hashes for the elements
array of the original documentation. Create your structure and save it in a constant:
# A carousel with two items (platform supports up to 10)
CAROUSEL = [
{
title: 'Random image',
# Horizontal image should have 1.91:1 ratio
image_url: 'https://unsplash.it/760/400?random',
subtitle: "That's a first card in a carousel",
default_action: {
type: 'web_url',
url: 'https://unsplash.it'
},
buttons: [
{
type: :web_url,
url: 'https://unsplash.it',
title: 'Website'
}
]
},
{
title: 'Another random image',
# Horizontal image should have 1.91:1 ratio
image_url: 'https://unsplash.it/600/315?random',
subtitle: "And here's a second card. You can add up to 10!",
default_action: {
type: 'web_url',
url: 'https://unsplash.it'
},
buttons: [
{
type: :web_url,
url: 'https://unsplash.it',
title: 'Website'
}
]
}
]
Then you can create an instance of UI::FBCarousel
by passing your template to the constructor. Then you can send it to the user.
carousel = UI::FBCarousel.new(CAROUSEL)
show(carousel)
Here is the the result:
Image Attachment
You can send an image to the user. Note that image won't have any text, but you can send a regular message along with it.
img_url = 'https://unsplash.it/600/400?random'
image = UI::ImageAttachment.new(img_url)
show(image)
Open Graph Attachment
The Open Graph template allows you to send a structured message with an Open Graph URL, plus an optional button. Currently, only sharing songs is supported. The song will appear in a bubble that allows the message recipient to see album art and preview the song.
song_url = 'https://open.spotify.com/track/5qqabIl2vWzo9ApSC317sa'
open_graph_element = UI::FBOpenGraphTemplate.new(song_url, [{title: 'Button', url: 'https://github.com/progapandist/rubotnik', type: 'web_url' }])
show(open_graph_element)
here's the result:
Other events
Events other then message
and postback
are currently not supported.
Deployment
Once you have designed your bot and tested in on localhost, it's time to send it to the cloud, so it live its life without being tethered to your machine. Assuming you already have a Heroku account and Heroku CLI tools installed, here's pretty much the whole process:
$ git init # if haven't done before
$ git add . && git commit -m "Ready to deploy!"
$ heroku create YOUR_APP_NAME
$ heroku config:set ACCESS_TOKEN=your_own_page_token
$ heroku config:set VERIFY_TOKEN=your_own_verify_token
$ git push heroku master
Now don't forget to go back to your Facebook developer console and change the address of your webhook from your ngrok's URL to the Heroku one. That's it!
π You're live! π
Coming next
-
More powerful DSL for parsing user input and binding it to commands.
-
NLP integration with Wit.AI (that allow for more things then the built-in NLP capabilities of Messenger platform, including a wrapper around Wit's interactive learning methods) is in the works and will be added to the gem some time in 2018...
Contributing
Im a still a relatively new Ruby developer (I started coding in 2015 while looking for a break from my 10+ years career as a TV reporter) and this is my first attempt at OSS. Any contribution will be more than welcome! Rubotnik is still in the early stage of development, so it's your chance to make a difference!
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests (currently project has no tests, but you're welcome to land a helping hand). You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
License
The gem is available as open source under the terms of the MIT License.