This post continues the series on creating a Slack app in Python,
picking up after adding an App Home view. A lot of the subtle, emergent
communication patterns within Slack happen by reacting to messages with emoji, and I thought it would be
fun to take advantage of that playfulness within the app we’re building.
Slack’s emoji reactions
or reacji are a common way for folks to interact with messages in a channel.
For the application we’re building, I was thinking it might be neat to add :ididit: and
:udidit: emoji, which folks could use to add items they did to their list of accomplishments
as well as use to add accomplishments to other folks' lists.
I’ve noticed some folks discount or dislike tracking their own accomplishments,
so this could be a playful way to get their team and community to help.
Add those emoji via the Customize Slack option in the top-left menu
within your Slack workspace.
From there click on Add custom emoji and create an image somehow.
I used Omnigraffle because I already had it open, but yeah, probably
you’ll use something else.
Add your image, name it :udidit: and click Save.
Then go ahead and do the same for the :ididit: emoji as well.
Now we have our custom emoji, and we just need to figure out how to
get notified when they’re used.
Subscribing to reaction_added events
Whenever an emoji is added, an associated reaction_added
event can be fired to the Events API, if you’ve subscribed to it.
To subscribe, head over to Event Subscriptions in your App dashboard,
open up Subscribe to workspace events and select reaction_added.
Remember to click Save Changes below, and reinstall your application
with those additional permissions.
Handling reaction_added events
Now that we’re receiving these events, we need to extend event_callback_event
to handle reaction_added events rather than erroring on them.
The first thing we want to do is to filter down to reactions
we’re interested in.
We only want to
act on the two reactions we added, :ididit: and :udidit:,
and we only want to handle reactions to message items,
ignoring files and what not.
def reaction_added_event(request, parsed):
event = parsed['event']
if event['reaction'] in ('ididit', 'udidit'):
if event['item']['type'] == 'message':
print("yes, handling this message")
Now that we’ve filtered down to appropriate messages,
we’re still not really sure about the contents of the message:
what did you or they actually do? To answer that, we’ll
need to make a call to conversations.history
as described in the Retrieving messages docs.
Adding channels:history scope
Before we can call the channels.history endpoint, we first need to request
the channels:history OAuth scope.
Go into your app dashboard, click on OAuth & Permissions, scroll down
until you see Scopes and then add channels:history.
After that, reinstall your app to request the additional OAuth scope.
We’ve been using the bot token so far, not the OAuth token, so we’ll
also need to the OAuth token to our env.yaml file. Your OAuth token
is in your App admin under OAuth & Permissions in the field labeled OAuth Access Token.
With that set, we can take advantage of our new scope.
We previously implemented the slack_api utility function to
simplify calling the Slack API, but it turns out that we can’t reuse
it easily for two reasons. First, conversations.history wants
application/x-www-form-urlencoded requests whereas the other endpoint
accepted application/json. Second, this endpoint wants a GET instead of
a POST, although that’s fixed easily enough by using the requests.requests function
which accepts the HTTP method as a string.
To work around those constraints quickly, we’ll write a get_message function which will call
into the API directly, instead of building on slack_api,
even though that’s a bit on the sad side.
What we really care about are messages/0/user and messages/0/text, which
we’ll be able to use to add this message either to the speaking or emojing
user depending on whether it’s an :ididthis: or udidthis respectively.
Pulling all together
Somewhat conspicuously, we still don’t have a database to store
all of this, and we’ll solve that in the next post, not this one. For now
we’ll create an interface for storing this data, the reflect function.
When I first started using Slack, I assumed reacji were a gimmick,
but then I remember the lightbuld going off when I first saw folks
organically start voting on a message using the :plus: emoji.
No one had asked them to vote, it just started happening, and it’s
that organic freedom, constrained with the rigid constraints (they are just small images
with a count next to them) that lead to so the novel usage patterns.
No Slack App ever needs to integrate with reacji, but I’ve seen a bunch
of creative integrations that acknowledge the patterns that folks already have
and then enhance those patterns with automated action.
Another great aspect of reacji as user interface is they are more discoverable
than Slash Commands, which are usually hidden from other users. Organic growth and
adoption are underpinning of a successful app, and reacji are a powerful mechanism
to that end.
I’ll say that I was a bit surprised at how long it took me to get reacji
working, because I’d come into this post assuming I was already done with
most of the necessary work and would just be introducing a new event.
Instead I needed to add a new style of API integration since conversations.history
didn’t support the JSON format, and a new API token since previously I’d been using the bot
token rather than the user token.
Individually, each of the Slack APIs are extremely well designed, it’s only
collectively that they start to surface some degree of friction.
This is a common challenge for broad, sophisticated APIs.
I’m currently reading Building Evolutionary Architectures,
which is better, more structured coverage of the ideas I wrote about in Reclaim unreasonable software. API deterioration can be prevented, but requires very deliberate usage of “asserted properties” in
my post’s nomenclature or “fitness functions” in Ford/Parsons/Kua’s.
We’ve now reached commit 08eb,
and are down to two more goals: integrating a database, and publishing this into the applications directory.
I’ll work on the database next, as it’s hard to publish an app that is exclusively stub data,
and then we can complete the publishing step.