A Simple, Maintainable Slack Bot

This past week we’ve begun automating our onboarding process by developing a chat bot for our team Slack. The bot messages new users with pre-written information which all users should be aware of, like relevant links (Code of Conduct, mentor calendar, etc.). We chose to use a Python implementation of the Slack API; Python is the standard language used at HSO.

Firstly, we had to choose a specific Slack API implementation for the bot’s core functionality. With multiple such libraries to choose from, the first (and most obvious choice) was Slack’s official Python bindings (python-slackclient). python-slackclient wasn’t the most reputable on GitHub, but we assumed it’d be comprehensive.

We set to work on writing a small proof-of-concept bot which would listen for join events in the data provided by the rtm_read() method of SlackClient from the library. The structure of the official library’s communication with the RTM aspect of the Slack API is such that continuous polling is needed in order to fetch new event data, which is represented as a list of dictionaries, with each dictionary corresponding to an event occurring in the bot’s presence.

One curious thing about this is that there only ever seems to be one event present in the list, even when multiple events have actually occurred between the last read, thus successive reads are needed in order to access them. This begs the question of why the events are contained in a list at all.

Event dictionaries have questionable key strings, for instance, the keys which are used to declare the nature of the event are dual, type and subtype, and it doesn’t seem like the subtype is really necessary, nor do the events using it use values consistent with those used for type. This is most likely a quirk of the Slack API itself and not this particular library, however.

The majority of the tasks which you’d expect to perform with the API are not implemented as native functions. Rather, there’s a “catch-all” approach which takes the form of a SlackClient method api_call which accepts a string of the API method and some keyword arguments which will be incorporated into the API call itself.

Not only are most API methods only accessible this way, but the api_call function returns not a dictionary of information, but bytes of JSON data which must be manually decoded before the success of the call can even be checked. This is inconsistent with the library’s behaviour with the rtm_read method, wherein the data is decoded before it is returned, and the user is presented with a dictionary ready-to-access.

Feeling quite discontented at this point with Slack’s own offerings, we tried out another (third party) library called slacker which we chose because it’s the most starred Slack project on Github for Python.

Though documentation is sparse for slacker (as with python-slackclient!), the structure of the library is much more friendly, and there exist class methods for the corresponding API methods, thus overall the experience when using slacker is a better one than with python-slackclient.

We’re now utilizing an alternative approach for detecting joined users after switching to slacker. Rather than listen for new join events, we’re instead storing the members we’ve “seen” inside a file and appending to it as necessary. This also prevents a member who leaves and rejoins a channel from receiving the introduction twice.

Initially, after completion, we suspected our bot didn’t work, as it continued to read in new user IDs endlessly, but we soon discovered this was merely due to how we were constructing a set of each line in the ID file, by omitting strip(), each ID would be read with its newline intact, causing it to be treated differently when compared against IDs retrieved from the network.

One issue that affected both libraries we tried was a lack of documentation, in the case of python-slackclient there is only a very simple example of the objects and methods within. slacker, on the other hand, stayed true to its namesake and merely linked to the Slack API methods on the website.

We believe Slack should adopt slacker as the official set of Python bindings to the API, and to facilitate this we are prepared to contribute by writing thorough documentation and better examples for slacker, as we feel that the profoundly more positive experience using it over python-slackclient helped bring this project to fruition. You can find our bot which now uses slacker on the organization Github account.

Special thanks go to Lillian for assisting with debugging, code revision and architecture, and Sara Murray for text content, ideas and help with testing.

Leave a Reply

Your email address will not be published. Required fields are marked *