Bots! Those nifty things that everyone is talking about. Which one day, with the right AI, could do most of our tasks for us (in the best case scenario).

This could be a post where I’d talk about a super specific bot that I’ve written. Or how bots are cool and everyone should write their own. But at the end of the post you would have more questions than answers and, at best, left with a momentary desire to write a bot.

Instead I’ll give you an overview of how you can write a Slack bot right away using a Ruby gem.

Why a Slack bot? Well, like it or not, a developer’s work has a great deal of team communication and Slack is a great platform for it. Let’s face it, we spend some time in it and it’s a great place to have a bot! Also, this platform has become more user friendly and allows you to do some customization.

Now imagine some of the tasks that you have to do when you start developing a new feature or work on a new issue. The sole act of opening a regular pull request can be a repetitive task. Choose the base branch, write a formatted title and add related links to the description. Even changing the state of your pull request or of an issue on your issue tracker.

Another example could be the aggregation of important information in a structured way. Either for code related tasks or non-work related information. For example, your favourite subreddits top posts of the day.

What to use?

Some time ago I found myself in need of a tool to quickly build a Slack bot. I had read an old post about Slack bots as slash commands, but it wasn’t quite what I was looking for. So I went through the Slack docs and found out that you need to interact with the Slack Real Time Messaging API (RTM API). This docs also suggested Botkit (a Node.js framework) to write your bots. But since I’m not the most proficient Node.js programmer, I decided to check what my options were in the Ruby land.

A super quick research led me to an awesome Ruby gem that goes by the name of Slack-Ruby-Bot. Couldn’t be more straight to the point!

The Slack-Ruby-Bot gem is built on top of the Slack-Ruby-Client gem, a more “close to the metal” approach. It allows you to write bots without having to worry about the details of the RTM API, such as message parsing. With this gem you can focus on your Slack bot commands logic.

How to do it?

The simplest way to use this gem is to write a class that represents your bot and its commands. But before I show how to do that there are some things you need to know.

Catching commands

There are two ways that your bot catches commands. The first one is by addressing the bot with the command, the explicit way. By addressing it I mean sending a message like you would with a normal user, as @bot-name. The second is by pattern matching, the implicit way. By this I mean the bot checks the messages for patterns and responds to it.

In the explicit way, the bot will respond to the commands specified after its name. To express this in your code you can use a code block with the command statement, for example:

command 'command_name' do |client, data, match|

# your code

end

With the command statement you can specify one or more words that will translate to a certain action. You can pass in arguments with your command, but they need to be at least one white space away from the former.

The implicit way has three statements to catch commands, the operator, match and scan.

operator '=' do |client, data, match|

# your code

end match /^Last Results of (?<team>\w*)$/ do |client, data, match|

# your code using match[:team]

end scan(/([A-Z]{3})/) do |client, data, teams|

# your code using caught teams initials

end

The operator statement works as the command statement, with some differences. It’s composed by one character and you don’t need a white space to separate the command and the arguments.

The match and scan statements are very alike, as they both work with regular expressions. The match statement is a bit stricter than the scan, as its pattern is better defined, check the example above. The scan statement catches a lot more information that can be disperse along a message.

First Setup

Before you start coding your bot there are two things that you have to do. First you need to get a SLACK_API_TOKEN so you can associate your bot with a Slack team. You can follow the instructions described in this link. After that you can setup your Gemfile with gems that you will use. The Gemfile should look something like this:

source 'https://rubygems.org' gem 'slack-ruby-bot'

gem 'celluloid-io'

gem 'httparty'

gem 'giphy'

The only essential gems are the slack-ruby-bot and the celluloid-io gems. The two remaining gems will be useful in our first example bot.

Simple Bot

As I mentioned before, the simplest way of building a Slack bot with this gem is by writing a Ruby class. This Ruby class needs to inherit from the SlackRubyBot::Bot class, provided by the gem.

Now, remember that I mention something about getting some posts from a subreddit? Well, as our first example we have a bot that fetches the first n posts from the hot page of a given subreddit.

Your class file must start by requiring the gems that you will use. In our example we need the slack-ruby-bot, httparty and giphy gem. Next you declare your class, you can call it whatever you want as long as it makes sense to you and your team. Then inside your class you define your command statements. Here I decided that I wanted to invoke my bot and, hence, make a explicit call. So I’ve used the command statement for my list command.

Taking a look at the command block we see that it has three arguments, the client, the data and the _match. These arguments have an important role on the command’s interpretation and response. The client argument is a Slack client object that enables all communications of your bot. The data argument is a hash that stores all the information about the incoming commands. It includes the type of the message, the ids of the sender, the channel, the team and the message itself. Also includes a timestamp attribute that you can use to analyse the latency of the requests. At last, the _match argument is an object that contains data about the command in a more structured way. It has a full command text with the bot id, but also breaks it into components. These components are specific to the type of the command statement. If you are using the command statement it includes the bot id, the command text and the expression. Here’s an example:

#<MatchData "<@BOT_ID> list ruby" bot:"<@BOT_ID>" command:"list" expression:"ruby">

The operator command statement _match argument has less information. As an implicit command, it doesn’t specify the bot id and so it only stores the command and the expression fields.

The content of the command’s block is pretty straightforward. It parses the command’s arguments to get the desired subreddits and makes a request for each one. But the most interesting part is the bot’s communication with the channel. To communicate with the channel we use the client argument in two different ways.

The simplest way, is to invoke the client’s say method that uses the channel data to write on the correct channel. In this example, I’ve used an option that let’s you send a gif alongside your message based on a string. Hence, the inclusion of the giphy gem that is obligatory to use this functionality.

The other way is using the client’s web_client with the chat_postMessage method. This method works the same way that the first one. But, unlike the client’s say method, the web_client allows embedded links in your text.

On the end of the file you can do some bot configuration. In this example we only enabled warnings in your bot’s logs. After the configuration you only need to call your bot’s run method to get it started.

Give your bot a try!

To try this bot you have to run the following command with your SLACK_API_TOKEN in your terminal:

SLACK_API_TOKEN=… bundle exec ruby reddit-bot.rb

This approach is fine if you only want to play with a bot or having it online when you’re online. If you want your bot to be available all the time for all your teammates then you’ll deploy it on a service like Heroku. You could use a web server application that helps Heroku from shutting down your bot. To really keep your bot up all the time you can use a Heroku scheduler to wake it up.

The Slack-Ruby-Bot gem has a tutorial that shows how you can integrate your bot with Sinatra and Puma. Based on that tutorial I’ve created a template to help you getting started faster.

Remember the idea of letting your bot create a pull request for you? I wrote a command to do it for Github pull requests using the octokit gem.

The boty repository includes the reddit-bot list command and the Github command. The Github command lets you list your repositories, branches and pull requests. Also, as mentioned before, it lets you create, merge and close your pull requests.