Recently I was pitched a revolutionary Slackbot idea. What if, with the power of Cloud Functions and Cloud Vision, I could take any image and dogeify it?
In this post, I'll show you how I built a Slackbot with Python that does exactly this. In addition, I'll show you how to overcome a common issue when working with APIs and how a unique Cloud Functions pattern can help you overcome this obstacle.
My slash command Slackbot consisted of one Cloud Function, doge-response. When someone types
/doge in their Slack client, Slack sends an HTTP event that triggers my Cloud Function with the text the user typed after my command. This text should be an image URL to dogeify.
The data flow looked like this:
Unfortunately, when it was time for testing, my Slackbot would think about things for a bit and return an operation_timeout error.
Timeouts are a problem for many services. Some give you 5 seconds, some even more, but in the end it sometimes isn’t enough for what you want to do.
After some research, I learned that Slack only gives you three seconds to respond to a slash command. Three seconds isn’t enough time to run a large image though Cloud Vision, create a new image, overlay Cloud Vision labels, and upload that image to Cloud Storage.
How did I get around these pesky timeouts? The solution is to get asynchronous, which wasn’t as hard as I thought it would be. By sending messages between multiple Cloud Functions, I was able to avoid API timeouts entirely.
The first step towards an asynchronous Slackbot was to split my single Cloud Function into two.
To facilitate messaging between these two functions, I tied in Cloud Pub/Sub. Cloud Pub/Sub is a real-time messaging service that allows you to send and receive messages between independent apps.
Cloud Function 1: doge-queue
Triggered by HTTP from
/doge slash Slack command.
- Verifies that the request is good
- Publishes the contents of the Slack response to my Cloud Pub/Sub topic, doge-convert
- Returns a “Working on that” message back to the Slack channel
Cloud Function 2: doge-response
Triggered by Cloud Pub/Sub publishings to the doge-convert topic.
- Decodes the Cloud Pub/Sub event data and now has the contents of the Slack request
- Dogefies the image
- Sends the image back to the slack channel where it was called using the response_url attribute of the Slack request
In the doge-queue Cloud Function, publishing one message to Cloud Pub/Sub takes less than 3 seconds, giving me time to send a response back to the Slack channel.
This message buys me 30 minutes to respond back to the Slack channel. While 30 minutes is awesome, I also need to think about other timeouts that could arise. Cloud Functions itself has a default timeout of one minute, which is plenty of time for what I need to do. If I needed more time, I could have updated my Cloud Function’s timeout to be up to 9 minutes.
After setting up a my Cloud Pub/Sub doge-convert topic and creating a pull subscription for doge-response, it was time to connect the dots between my two Cloud Functions.
doge_queue function, I dumped the contents of the Slack request into a JSON object.
publisher_client.publish() then takes my topic path and the JSON object (encoded), publishing it to my topic.
publisher_client.publish() to a variable,
future, I can check the
future.result to confirm the message was published successfully.
At this point, a message sent with my Slackbot is now in my doge-convert topic. Once that message hits the topic, doge-response is triggered by the event.
doge_response function, I grab the
data out of the event and decode it.
With just one line of code, I now have the contents of my Slack request to work with in my second Cloud Function.
And that’s it! By having my first Cloud Function send an initial message back to Slack while it publishes the contents of the Slack request to Cloud Pub/Sub, I buy my second Cloud Function plenty of time to do all the heavy lifting. Goodbye 3 second API timeout limitation!
If you want to see all the code for this Slackbot, including how I handled image storage, called the Cloud Vision API, and handled text overlay, check out my GitHub repo!