Boty
Boty
is a utilitary to create bots (at this time, specificaly Slack bots).
A bot in the context of this gem is an ordinary ruby application that knows how
to connect and receive messages in a Slack company room. Boty
will give you a
nice api to bind your own logic to specific messages of your interest.
Usage TL;DR version:
- create new slack bot integration (http://[company].slack.com/services)
$ gem install boty
$ boty new jeeba
(wherejeeba
it's a name that you will choose for the bot in the integration step)- type your company name (the one used in the slack url: http://[company].slack.com)
- type the api key created on the bot integration step
$ cd jeeba
(the dir created after the name passed as parameter to thenew
command- ./bot
Your bot should be up and running, which means: connected to your Slack room under the name configured in the Slack Bot integration.
Custom scripts
On the script
dir of your bot there is a example ping
script. But you will
want to create your own scripts:
- stop the bot (if it's already running)
- add a new file named
hello.rb
into thescript
dir - add a
command
to made it available via bot mention (@jeeba
in my case) - add a
hear
instruction so thebot
listen to any message with the pattern configured- use the method
#say
to instruct thebot
to send a message in the channel - use the method
#im
to instruct thebot
to send a private message
- use the method
# script/hello.rb
command "hi" do
say "hi #{user.name}"
end
command "im me" do
im "want something?"
end
hear "say something" do
say "something"
end
- run the bot
./bot
- on the slack room, issue the commands and send messages:
valeriano 12:14 AM
@jeeba: hi
jeeba BOT 12:14 AM
hi valeriano
valeriano 12:14 AM
@jeeba: im me
# a private message will arrive:
jeeba BOT 12:14 AM
want something?
# back to the general channel:
valeriano 12:16 AM
say something
jeeba BOT 12:16 AM
something
Regexes
There is an api to capture matches when regexes are used on hear
and command
configurations:
- stop the bot (if it's already running)
- add a new file named
math.rb
into thescript
dir - add the
command
/sum (\d+) (\d+)/
:
# script/math.rb
command(/sum (\d+) (\d+)/) do |augend, addend|
say "(#{augend} plus #{addend}) = #{augend.to_i + addend.to_i}"
end
- run the bot:
./bot
- on the slack room, issue the command:
valeriano 12:30 AM
@jabberu: sum 100 200
jabberu BOT 12:30 AM
(100 plus 200) = 300
And this is pretty much it! :tada:
If you need more details about any of the features in Boty
, you probably will
find it in this README, on the sections that follow.
If you don't find what you need, please contact us or even create an issue. Our
goals with this project are to make it usefull and easy to use, check the
contributing info for more.
Usage (long version)
Now you will learn how to create a custom bot application for your needs, how to configure it to allow the bot say stuff in your slack channels and finally, will see how to run the bot properly.
Slack Bot Integration
The first thing to do is to create a Slack Bot integration.
Go to http://[your-company-name].slack.com/services
to add a new one.
In the process an API Token
will be generated. That will be used in the next
step.
Installation
Now you can install Boty
:
$ gem install boty
This will give you an executable boty
, which ships with a command help
so
you can know all the stuff that it can do. But the main one is to create your
new shiny bot.
Creating a new bot
To create a new bot, execute this on your terminal:
$ boty new jeeba
Where jeeba
will be the nickname you give your bot in the Slack
integration.
The command will create a new directory after your bot name (jeeba
in my
example). Your bot application will live in this directory. Feel free to check
the contents, it will have some ruby files, some directories... it's just an
ordinary ruby application.
But first, let's see something about the configurations.
Configuration
When executing the boty new
command, you will be prompted for the
company name and the bot api key.
The information that you enter will be used only locally, so you can experiment
and test (we will see more about the automated ones in this README) while
developing your bot. This will be stored in the .env.local
file on the
recently created dir.
If you want to understand how this configuration is managed locally, you can
check de Dotenv
gem documentation, but
you don't need to worry about it, if you don't want.
Now, let's create some commands and message listeners on your bot.
Creating a Session (and the bot
file).
Since you already have a Boty
project configured with your company name and
api key, you should be able to start a new session for your bot by calling
Session#start
.
This is a "blocking" call. It will hang to allow the bot to listen for messages and send them. You can pass a block to it in order to configure your bot preferences, or knowledge if you prefer =).
The block passed to #start
will be yielded in the scope of a Bot
. In other
words, you can call any bot method inside this context. Note that at this time,
you can consider your Bot
already connected to the Slack room.
session = Boty::Session.new
session.start do
# configure your bot here
end
In your project there is a bot
executable file. This file already creates a
Session
with a minimum bot configuration: a listener with the bot name. Which
means that any message with the bot name will trigger the behavior in this
configuration.
This is just to save you time. You could start a Session by hand, if you wanted, so feel free to check the code on this file and change it at will =).
Now it's time to better understand what is a listener, a command and how to use regexes to configure the patterns that trigger the bot.
Command - a bot mention followed by a instruction pattern
A command
can be understood as a direct instruction to the bot. To be issued
the bot should be mentioned by the message using the default Slack mention
"operator" @
.
To create a command, call the method #command
on the bot passed as parameter
in the Session#start
method. The command
method receive the pattern for the
command, and the block that should be executed when the command is issued.
See how to teach your bot to flip tables like a pro.
session = Boty::Session.new
session.start do
# ...
command("flip") do
say "HEY! (╯°□°)╯︵ ┻━┻"
end
end
Now, to see the result, with the bot running, send in any channel where the bot is (the #general is the default):
@jeeba: flip
(remember: jeeba
is the name that I choose for my bot, you should use the
correct name here).
The previous command will result in the following response:
jeeba BOT 2:25 AM
HEY! (╯°□°)╯︵ ┻━┻
There is an alternative method to create a command
: bot#response
. Use the
one more appealing to you, they are the same.
The bot#say
method used in this example is better explained here.
Wrapping up:
- a command is a pattern triggered by a bot mention
- you can configure your bot patterns in the block passed to
Session#start
Now, let's see how a listener works.
Listener - a message pattern that the bot should respond to
A listener is used when the bot is interested in ANY messages containing some pattern. Note that a listener will not be triggered only when there is a bot mention, but anytime a message containing the pattern is sent on a channel where the bot is.
Simple enough, let's get to the code:
session = Boty::Session.new
session.start do
# ...
hear("what's going on here?") do
say "I don't know. But something is always wrong. :scream:"
end
end
This configuration says that anytime a message containing the string "what's going on here?" is sent, the bot will respond. So a valid test could be send the following message in the #general channel:
valeriano 3:57 AM
OMG guys, what's going on here?
And the response will be, as expected:
jeeba BOT 3:57 AM
I don't know. But something is always wrong. :scream:
Wrapping up:
- A listener is triggered for any message containing the pattern
- A listener don't need a bot mention, so careful: the range of this config is wide
Before we start to study the "script" way of configuring command
and
listener
binds, let's see how we can use ruby regexps to capture message
parameters.
Regexes - a pattern that allow capture parameters within a message
What is a bot if it can't annoy people when we want it to? So let's teach our bot to send private messages to people in the Slack room.
Let's create an im
command that will be capable of extract the person to
whom we want to send a message, and the message that we want the bot send. Our
bot will be capable of understand the following instruction:
valeriano 2:44 PM
@jeeba: im julian with omg! lol! bbq!
The command configuration can be like this:
command /im (\w+) with (.*)/ do |person, |
im , to: person
end
The regex matched portions will be passed as parameter to the block given to command.
And this is it! :tada:
Commands, Listenners and aliases
Both commands and listeners can be aliased, which means that the same
Action
can be triggered by different regexes:
command /hi/i, /hello/i do
say "Ohay #{user.name}! Hello there."
end
valeriano 12:04 PM
@jeeba: hi
jeeba BOT 12:04 PM
Ohay valeriano! Hello there.
valeriano 12:05 PM
@jeeba: hello
jeeba BOT 12:05 PM
Ohay valeriano! Hello there.
Adding custom scripts
Now you already know what are listeners and
commands. It's time to know another way to create those "binders",
it's what we call scripts
.
Any ruby file available on scripts
will be loaded when a Session
starts.
Your bot ships with an example script named script/ping.rb
, with a very simple
example of what you can do. But let's create a new script from scratch.
Create a new file script/flip.rb
on your bot directory. Any method available
in a bot will be available in this file, which means you can use command
,
hear
, im
, say
, message
, etc.
Let's code:
# script/flip.rb
command "flip" do
say "(╯°□°)╯︵ ┻━┻"
end
And this is pretty much it. When your bot runs, it will be able to perform the
command flip
:
valeriano 3:36 PM
@jeeba: flip
jeeba BOT 3:36 PM
(╯°□°)╯︵ ┻━┻
So feel free to customize the bot
file or create your own scripts. What please
you more is what you should use.
DSL for bot scripting
In this section you will see all the methods available to configure your bot
.
bot#say
- Sending public messages on Slack channels
Use #say
when the bot should send a message in the channel that triggered the
configuration block:
command "flip" do
say "(╯°□°)╯︵ ┻━┻"
end
If the bot wants to start a conversation, or even just say something in a
channel without have received a message (via #hear
or #command
), just call
#say
inside a script.
say "ready to turn tables", channel: "#general"
command "flip" do
say "(╯°□°)╯︵ ┻━┻"
end
If a channel isn't passed to #say
it will defaults to "#general"
. You can
use any Slack RTM postMessage
parameter when calling #say
.
bot#im
Bots can send private messages. Use the #im
method:
command "who am I?" do
im "you are #{user.name}!"
end
In the previous example, the private message will be sent to the user that
issued the command, this is a default. If you want the bot to send a message to
a specific user, just indicate the user name via the to
option:
command "annoys julian" do
im "Loren Ipsum, Julian boy!", to: "julian"
end
bot#message
The message that triggered a block is made available in the block's scope in the
method #message
:
command "what did you say?" do
say "Well: #{.text}"
end
Execute the previous command will be as follows:
valeriano 12:07 AM
@jeeba: what did you say?
jeeba BOT 12:07 AM
Well: @jeeba: what did you say?
Note that the #text
returns the raw message that triggered the handler,
including the bot mention itself.
bot#user
If the bot need to access the data about the user that send the message that
triggered the handler, use the #user
method.
Let me borrow an already used example:
command "who am I?" do
im "you are #{user.name}!"
end
bot#http
There is an utilitary method #http
available on the handler block. This guy is
useful to make, well... http requests. It supports all http verbs and return a
Hash
if the http response contains a "application/json"
Content-Type
header, or else the raw body.
Let's create a command
that fetches a xkcd strip for us:
command /xkcd(\s\d+)?/ do |number|
number = number ? number.strip : "1"
xkcd = http.get "http://xkcd.com/#{number}/info.0.json"
say "#{xkcd["alt"]}\n<#{xkcd["img"]}>"
end
To execute this command:
valeriano 12:40 AM
@jabberu: xkcd 2
The strip number 2 of xkcd will be brought to the room. If we don't pass a number parameter, the strip number 1 will be assumed as default.
If something goes wrong with the request, you can use the http#response
method
to access the inner response object. It'll be a
faraday http response object.
bot#desc
- Describing your bindings
A bot list all the commands and message handlers that it knows in the moment. If
you want to give a nice description and/or a usage tip on command you can use
the desc
method.
Given that your bot has the following script:
desc "pug me", "Send some nice pug in the channel."
respond(/pug me/i) do
# ...
end
The follow text will be part of the response for a @bot: knows
command:
pug me: Send some nice pug in the channel.
You can use just the description if you want. In this case the regex
itself
will be used as the command name.
desc "Send some nice pug in the channel."
respond(/pug me/i) do
# ...
end
valeriano 2:25PM
@bot: knows
bot 2:25PM
knows: List all the commands known by this bot.
/pug me/i: Send some nice pug in the channel.
We strongly recommend that you describe all of your scripts. But if you don't,
the bot will be capable of tell you what regexes
are binded to it:
valeriano 2:47PM
@jabberu: knows
jabberu 2:47PM
knows: List all the commands known by this bot.
/pug me/i
/jabberu, are you there\?/i
Testing your own scripts
todo: document
For now, check the spec/script/ping_spec.rb
and follow it's leads.
Running locally
After create the new bot application and give the bot api key, you can just enter the new dir and run
$ ./bot
Done! Your bot will be connected to your company Slack through the nickname that you provided in the integration step. To see if it's working properly, just go the the slack, in the general channel (or any other where the bot was invited) and type:
@jeeba: ping
It will respond to you: pong
. IT'S ALIVE.
Deploy on heroku
But probably what you want is to have your bot running in a server, not in your machine, right?
Your bot is created with an example Procfile
to make it easy to run it on
Heroku (to give an example).
Create a new project on Heroku, following their instructions, until they ask you
to do the "deploy" (the git push heroku master
).
configure the api key
Now you need to add the configurations that are localy stored on .env.local
.
The two environment variables on that file are:
SLACK_COMPANY
SLACK_BOT_API_TOKEN
The Heroku command line tool offers a way to create environment vars in the server, check their documentation. Today, when as I'm writing the readme, you can use the following commands to set those two environment variables:
$ heroku config:set SLACK_COMPANY=your-company-name
$ heroku config:set SLACK_BOT_API_TOKEN=your-bot-integration-api-token
allow process to run
Heroku will not detect a web application in your bot, so you need to tell them to run your application as a, well... as a "normal" application. Go to your heroku dashboard (https://dashboard.heroku.com/apps), find the application that you have created to your bot. On the tab Resources, find a line with the information:
your-bot-name bundle exec ./bot
Turn on this resource. And done, your bot is up and running!
Using the logger
Boty
ships with a builtin logger that can be used in your own scripts. It is
made available via #logger
.
This will return a instance of a Ruby Logger.
Note that the default logger implementation will write to the STDOUT
.Let's
see a usage example:
session.start do
command(/hello/i) do
logger.debug "saying hello"
say "hello there."
end
end
By default the logger will write on the standard output, the previous command, when triggered, will produce the following log line:
D, [2015-12-11T00:13:28.712380 #40369] DEBUG -- : saying hello
Of course, if you need to write to a file, instead of the STDOUT, just change the adapter for your logger.
Log into files (instead of STDOUT)
Before start your bot session, configure the file log:
Boty::Logger.adapter = Logger.new("log/output.log")
And this is all you need! :tada:
Logger adapters
There is an adapter included on Boty
that can be used by you and is also a
good example of how to create a custom logger adapter for Boty
.
Supose that you want to write to a file and still send the logs to the STDOUT
.
You can use the Multi
adapter like this:
Boty::Logger.adapter = Boty::Logger::Multi.new([
Logger.new(STDOUT),
Logger.new("log/output.log")
])
And this is it. Now when you call logger.debug "some message"
from your bot,
this log line will be writen to the "log/output.log"
file and also in the
STDOUT
.
You can write you own log adapter if you want. The Multi
adapter
implementation is fairly simple. Let's use it as an example of how to write your
own adapter.
You can extend the ruby Logger class and worry yourself on write the #add
override:
class Multi < ::Logger
def initialize(adapters)
@adapters = adapters
end
def add(*args, &block)
@adapters.each do |adapter|
adapter.add(*args, &block)
end
end
end
The add method is called internaly by Logger and has all the information that
the auxiliary methods like #debug
, #info
etc received when called.
The Multi adapter implementation just delegate this call to the underlying adapters passed as parameters for the constructor.
For more information on the #add
parameters, check the ruby doc.
Multi also allows you to change the level for all the underlying adapters at
once, the #level=
overriten implementation is like this:
def level=(level)
@adapters.each do |adapter|
adapter.level = level
end
end
I18n
The descriptions for the commands and listeners that ship with Boty
can be
customized via the I18n gem mechanics.
Boty
ships with translations for :en
and :pt-br
. If you want to override
those messages, you can take a look at the files on locale
.
I'll recomend that you just copy and paste the file that you want to customize
on the locale
dir of your project, and them make the editions that you want.
Of course, you can add translations for any language that you want on your
locale
dir. And even add any translation that you want for your own
command descriptions.
To instruct your bot about which language to use, set the Boty.locale
configuration and you're done.
The bot
executable created on your project generated by boty new
have the
following line before the session start:
Boty.locale = ARGV.pop || :en
This means that you can start your bot with a command line argument to tell which is the idiom that this session should use. This allows you to have the same bot running in different sessions with different idioms. The following usage is totally fine:
./bot pt-br
Development
After checking out the repo, run bin/setup
to install dependencies. 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
.
The local ./bin/bot
The project have a ./bin/bot
executable that should be the product of
compiling the template/project/bot.tt
erb file. This is an easy way to just
run the bot using the same logic that a project generated by bot new
will use.
It's highly recomendable that you test your changes using this approach before
submit a PR
or generate a new release. =)
Compiling the ./bin/bot
There is a bot:compile
rake task that compiles the template right into the
bin
direcotry and make it executable (with chmod +x
).
Code guidelines
Just make sure to run bundle exec rubocop
before submit any PR
, good coding!
:D
Contributing
Bug reports and pull requests are very welcome on GitHub at https://github.com/ricardovaleriano/boty. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.