Daniel Doubrovkine bio photo

Daniel Doubrovkine

aka dB., @awscloud, former CTO @artsy, +@vestris, NYC

Email Twitter LinkedIn Github Strava
Creative Commons License

In 2020 Slack introduced granular permissions and has begun enforcing that legacy applications use them. I’ve previously migrated a simple bot to use those in-place, but it has come time to rewrite my larger, more complex S’Up for Slack Teams and GameBot Leaderboard bots. Furthermore, the previous versions of the bots only allowed one S’Up group and one leaderboard per team, so I decided to take this opportunity to add support for actions and user settings in both DMs and individual channels.

Here’s a rundown of the code of Slack Gamebot. It’s MIT-licensed, open-source. Install it from gamebot2.playplay.io.

Components

The bot consists of several major components.

Models

The database models include Admin (a Slack user outside of a channel), Channel, and User (a user that belongs to a Slack channel). While users in Slack are global, my bots store different information for each user in every channel. Other models are specific to the business logic of the bots.

Commands

The Slack commands are handlers of mentions from slack-ruby-bot-server-events-app-mentions. In addition to the out-of-the-box functionality provided by that library, I wrapped my commands with mixins that create or fetch existing user and channel information from the database on every request.

APIs

Slack sends events to a set of API endpoints which are implemented using Grape in the slack-ruby-bot-server-events library. My bot also exposes a complete Hypermedia REST API for its own data. Together these endpoints are mounted under /api.

Website

Users first land on an ERB template website that is served using rack-server-pages.

Configuration

The bot configuration includes configuration files for MongoDB and NewRelic, as well as various initializers, such as OAuth scopes for for slack-ruby-bot-server, Stripe, and Giphy. There’s a funny wrapper for slack-ruby-client that injects animated GIFs into everything the bot says.

Bot Startup

The bot starts from config.ru with foreman start. All above-mentioned components are required from app.rb. The code executes pre-start bot actions with SlackGamebot::App.instance.prepare!, prepares the API with SlackRubyBotServer::Service.start!, and finally runs the middleware that responds to HTTP requests with run SlackGamebot::Api::Middleware.instance. Basically, the bot startup ends with exposing a Rack web server on port 5000 when run locally.

Bot Installation

Users that click “Add to Slack” on the public website are redirected to a Slack OAuth v2 workflow, allow the bot to be installed, and are redirected back with a codeparameter that is then POSTed to the bot API, which creates a Team. Slack then begins sending events to the API endpoints.

Slack Interactions

Bot Mentions

Slack sends mention events to the bot which are handled by the slack-ruby-bot-server-events-app-mentions library. The payload includes the current user and channel, and the library routes the request to the appropriate command handler. My bot wraps mentions in a mixin to check for a valid paid subscription (bots are free to try for 2 weeks) and fetch user and channel information from a database as mentioned above.

Members Joining and Leaving Slack Channels

Slack sends events to the bot every time users join or leave a channel, handled by the slack-ruby-bot-server-events library. My bot handles that here, in a callback.

Users Opening App Home Tab in Slack

This is a special interaction that happens when a user opens the bot home tab, handled in a callback. We want to send a message to the user the first time they do that, so the handler creates a Channel and a User record and only sends a welcome message if those don’t exist.

Users Mentioning the Bot without @

This is an interesting pattern that is not natively supported by Slack events. I want users to be able to give the bot aliases. For example, instead of always having to type @gamebot (a Slack mention), users can type pongbot or pp. To implement this, I subscribe the bot to message events (requires channels:history), parse every message for one of the known bot names or aliases stored in the database, and hand the results back to the command handlers as if they were typical app mentions.

Code for Slack GameBot is here, and the code for S’Up is here. Try the bots out from sup2.playplay.io and gamebot2.playplay.io.