# Boty
[](https://travis-ci.org/ricardovaleriano/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](#integration) (http://[company].slack.com/services)
- `$ gem install boty`
- `$ boty new jeeba` (where `jeeba` 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](#integration)
- `$ cd jeeba` (the dir created after the name passed as parameter to the `new` 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 the `script` dir
- add a `command` to made it available via bot mention (`@jeeba` in my case)
- add a `hear` instruction so the `bot` listen to any message with the pattern configured
- use the method `#say` to instruct the `bot` to send a message in the channel
- use the method `#im` to instruct the `bot` to send a private message
```ruby
# 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 the `script` dir
- add the `command` `/sum (\d+) (\d+)/`:
```ruby
# 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](#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).
- ![](docs/images/readme-01-screen-integration.png)
- ![](docs/images/readme-02-screen-integration.png)
- ![](docs/images/readme-03-screen-integration.png)
### Installation
Now you can install `Boty`:
```sh
$ 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](#usage).
### Creating a new bot
To create a new bot, execute this on your terminal:
```sh
$ boty new jeeba
```
Where `jeeba` will be the nickname you give your bot in the [Slack
integration](#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).
### 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](https://github.com/bkeepers/dotenv), 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.
```ruby
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.
```ruby
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](#bot_say).
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:
```ruby
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:
```ruby
command /im (\w+) with (.*)/ do |person, message|
im message, to: person
end
```
The regex matched portions will be passed as parameter to the block given to
_command_.
And this is it! :tada:
### Adding custom scripts
Now you already know what are [listeners](#listeners) and
[commands](#command). 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:
```ruby
# 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:
```ruby
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.
```ruby
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](https://api.slack.com/methods/chat.postMessage)
parameter when calling `#say`.
#### `bot#im`
Bots can send private messages. Use the `#im` method:
```ruby
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:
```ruby
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`:
```ruby
command "what did you say?" do
say "Well: #{message.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:
```ruby
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](http://xkcd.com/) strip for us:
```ruby
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](http://xkcd.com/) 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](https://github.com/lostisland/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:
```ruby
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.
```ruby
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
```sh
$ ./bot
```
Done! Your bot will be connected to your company Slack through the nickname that
you provided in the [integration](#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:
```sh
@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](http://heroku.com) (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](https://devcenter.heroku.com/articles/config-vars).
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](http://ruby-doc.org/stdlib-2.2.3/libdoc/logger/rdoc/Logger.html).
Note that the default logger implementation will write to the `STDOUT` .Let's
see a usage example:
```ruby
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:
```ruby
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:
```ruby
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`
overrite:
```ruby
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](http://ruby-doc.org/stdlib-2.1.0/libdoc/logger/rdoc/Logger.html#method-i-add).
_Multi_ also allows you to change the level for all the underlying adapters at
once, the `#level=` overrite implementation is like this:
```ruby
def level=(level)
@adapters.each do |adapter|
adapter.level = level
end
end
```
### I18n
**todo: document**
## 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
### Code guidelines
## 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](contributor-covenant.org) code of conduct.
## License
The gem is available as open source under the terms of the
[MIT License](http://opensource.org/licenses/MIT).