Serverless Slack Apps and Slash Commands
Tags: serverless, devops
Published: 2020-02-10 20:58:47

It's crazy to think that I entered the Developer Advocate field almost a year ago. Through that year, I’ve worked to ease the pains that companies and developers had together. I attended meetups, conferences, and hackathons, seeing new companies and products vying for developer attention and adoption. I’ve been happy to work with some of these productivity tools that are hard to go without once used. 

Working with serverless technologies has sped up my development over the past year, and I recently used them in creating Slack Apps and slash commands  Now come, walk with me, as I show you different methods of serverless and how to search google images via a Slack slash command.

## Table Of Contents
1. [Falling in love with serverless](#falling)
2. [Serverless development for Slack-ers](#slackers)
3. [Traditional Slack Apps](#bonein)
4. [Boneless Slack Apps](#boneless)
5. [Search Google Images in Slack](#search)
5. [How does it look?](#how)

## Falling in love with serverless <a name="falling"></a>

Some favorite tools of mine that take a lot of work off my shoulders are serverless platforms. The fact that I don’t have to worry about where or how my code is hosted is amazing to me. Even compared to setting up cloud containers, it’s a whole different ball game. 

Although a few serverless providers have become household names in my stack, I’ve only started using them within the last year. Now given the chance between handling the server set up myself or utilizing a cheap/free trial of a serverless API, I’ll always choose the latter. When my project needs to scale, the serverless providers can handle it and they provide a much-needed abstraction over standard cloud providers.

My personal choice for Slack app development is [Nimbella Commander](https://nimbella.com/product/commander), a Slack app that allows you to create serverless functions and connect these functions to your Slack environment. This handles much of the integration and infrastructure that other serverless platforms do not touch.  We'll discuss creating Slack Apps using both the traditional serverless platforms and using Nimbella Commander.
 
## Serverless development for Slack-ers <a name="slackers"></a>
  
My foray into serverless applications was rewarding, being able to develop my projects using various Functions as a service (**FaaS**) products was a breeze. My next step was leaping, unhindered by servers, into Slack App development.

I’ve used Slack through college, and it played even more of a role in my life since I joined the workforce. Slack provides a platform to create apps, some of which integrate other services into your favorite team collaboration chat environment. Creating an app inside it requires some research into the [Slack API](https://api.slack.com/) and a way to process and send information to the endpoints provided by Slack. That last section, your code that connects to your Slack workspace via the Slack API, doesn't have to be your traditional hardware in a basement solution. We can use a serverless option to run our application in Slack.

## Traditional Slack Apps <a name="bonein"></a>

Using a serverless solution to connect to your Slack app can save you time, money, and worry. These benefits are similar to making the original switch from a traditional server/cloud platform. Worrying about scaling with your user base is a thing of the past, but you can also skip setting up the connections and storage of your cloud providers. By ignoring the setup time, it allows us to focus more on the use of each serverless function. 

Your code, in the serverless cloud providers environment, will directly call the Slack API. The functions you create will need to [manage authentication (OAuth 2.0)](https://api.slack.com/docs/oauth), [enable user interaction](https://api.slack.com/interactivity/entry-points), and need to implement each feature of your Slack App. This method is already light years better than provisioning your servers and organizing the multitude of clusters your growing app will need. It will take work to start the connection to Slack and it is harder to get someone else set up with your code, but this method can be rewarding and allow complete customizability.

## Boneless Slack Apps <a name="boneless"></a>

I’ve especially enjoyed our third option for serverless Slack App development discussed above, Nimbella Commander. An app that helps you create other Slack apps? It takes care of all of the infrastructure that your app needs when thrown into a Slack community. It allows for the same mindset when developing these serverless functions, but lets you forget about parameter parsing, authentication/logging in, and has other features like automation, logs, and access control. It’s easy to get it set up and you can create your first slash command in a couple of minutes.
  
For those who help manage their businesses' cloud infrastructure, there are [command sets](https://github.com/nimbella/command-sets) that integrate with AWS, DigitalOcean, and Vultr. These DevOps commands are already hosted publicly on GitHub. If you wanted to get started with a simple way to integrate your code/API into the Slack environment without building the Transcontinental railroad of infrastructure please try it out. 

## Search Google Images in Slack <a name="search"></a>

To query Google programmatically, we need to complete a few steps:
1. Create a Custom Search Engine
2. Get a Google JSON API key
3. Setup your serverless environment
4. Google API Call
5. Formatting!

### Create a Custom Search Engine
Google doesn't provide an API to search google but will let you create your custom-defined search engine, specifically limiting the search to particular sites you define. We don't want this, but we can do a little hacky magic to navigate around that roadblock. Check out Google's full [Custom Search Engine tutorial](https://developers.google.com/custom-search/docs/tutorial/introduction) for more details, but I'll quickly go over what you need to know below.
First, [create a custom search engine](https://cse.google.com/cse/create/new) with the sites to search as any random website you want (I used www.google.com). It doesn't matter what you put in this field, as long as you can create your custom search engine. 
After creating your CSE, click the bottom button that takes you to the control panel. Make sure that ImageSearch is enabled, and delete the sites listed under "Sites to search". This was the "hacky magic", we tell google we want to search that site, but then delete it. Your CSE is now not limited to any specific sites.

![Custom Search Engine Creation](https://dev-to-uploads.s3.amazonaws.com/i/cls4zhu4k28zc9b680rw.png)

The last thing we need from this page is the Search Engine ID. Make sure its handy for when you need it soon!

### Get a Google JSON API key

Check out the [Custom Search JSON API](https://developers.google.com/custom-search/v1/overview#api_key) to get a key from Google. You'll need a project for this key to be under so make sure you can create one on your account. 

### Setup your serverless environment

It's time for you to set up your serverless environment and its connection to Slack. I'll be using Nimbella Commander so I don't have to worry about any setup besides making sure I'm an admin in my workspace, [adding it to Slack](https://slack.com/oauth/authorize?client_id=876870346995.892105847680&scope=commands), and using `/nc dapp_add` to set up the default app. Review the [Nimbella Commander documentation](https://nimbella.com/resources-commander/quickstart) for more detail while setting up. 

Once we have **dapp** installed, we can create our command. We'll want to allow users to input a query along with the index of the image in the results. If we search the same thing and take the same index of the results we'll often get the same result. Allowing the user to type in an image index gives them choice. Create this command with `/nc command_create g_image <search> [<i_index>]`.

Nimbella Commander will give you a link to access their code editor. I usually develop in my IDE of choice (VSCode ftw) and then insert my code into their editor. Let's get started writing this command!

### Google API Call

With our editor open, we can now insert our code! I'll first show you the platform-agnostic API call.

```js
let url = "https://content.googleapis.com/customsearch/v1?cx=" + customSearchEngine + "&q=" + search + "&searchType=" + "image" + "&key=" + googleSearchKey;
let str = await getContent(url);
let searchJSON = JSON.parse(str);
let images = searchJSON.items;

```
You need to be sure to insert your CSE ID, the search term from your use, and your Google Search JSON key into the URL. This also parses the JSON we receive and puts all the items into **images**. 

While using Nimbella Commander, you can place your API keys into encrypted storage so there's no chance of anyone accidentally posting them to GitHub. I did so by following the [documentation](https://nimbella.com/resources-commander/guide#secrets).

I also use an async function named getContent to create and handle GET requests as well.
 
```js
const getContent = (url, headers) => {
  // Return new pending promise
  return new Promise((resolve, reject) => {
    const request = require('https').get(url, {
      headers
    }, response => {
      // Handle http errors
      if (response.statusCode < 200 || response.statusCode > 299) {
        reject(
          new Error('Failed to load page, status code: ' + response.statusCode)
        );
      }

      // Temporary data holder
      const body = [];
      // On every content chunk, push it to the data array
      response.on('data', chunk => body.push(chunk));
      // We are done, resolve promise with those joined chunks
      response.on('end', () => resolve(body.join('')));
    });
    // Handle connection errors of the request
    request.on('error', err => reject(err));
  });
};
```

### Formatting!
Before we get into organizing how our response looks to the user, let's make sure we handle if the user inputs an image index that's either too high or low.

```js
const {
  search,
  num,
} = params;
const {
  customSearchEngine,
  googleSearchKey
} = secrets;


let index = 0;
if (num) {
  if (num > 9 || num < 0) {
    return {
      response_type: 'in_channel', // or `ephemeral` for private response
      text: "Please choose an image range between 0 and 9"
    };
  }
  index = num;
}
```

If they do, we just return a message explaining the range we accept.

The Slack API has tons of ways to interact and show off your content to users. Sending messages back to users after they call your slash command is one of the most direct and easy interactions you can create. Check out the [Message builder](https://api.slack.com/docs/messages/builder?msg=%7B%22attachments%22:%5B%7B%22fallback%22:%22Required%20plain-text%20summary%20of%20the%20attachment.%22,%22color%22:%22#36a64f%22,%22pretext%22:%22Optional%20text%20that%20appears%20above%20the%20attachment%20block%22,%22author_name%22:%22Bobby%20Tables%22,%22author_link%22:%22http://flickr.com/bobby/%22,%22author_icon%22:%22http://flickr.com/icons/bobby.jpg%22,%22title%22:%22Slack%20API%20Documentation%22,%22title_link%22:%22https://api.slack.com/%22,%22text%22:%22Optional%20text%20that%20appears%20within%20the%20attachment%22,%22fields%22:%5B%7B%22title%22:%22Priority%22,%22value%22:%22High%22,%22short%22:false%7D%5D,%22image_url%22:%22http://my-website.com/path/to/image.jpg%22,%22thumb_url%22:%22http://example.com/path/to/thumb.png%22,%22footer%22:%22Slack%20API%22,%22footer_icon%22:%22https://platform.slack-edge.com/img/default_application_icon.png%22,%22ts%22:123456789%7D%5D%7D) or the [Block Kit builder](https://api.slack.com/tools/block-kit-builder?mode=message&blocks=%5B%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22Hello,%20Assistant%20to%20the%20Regional%20Manager%20Dwight!%20*Michael%20Scott*%20wants%20to%20know%20where%20you%27d%20like%20to%20take%20the%20Paper%20Company%20investors%20to%20dinner%20tonight.%5Cn%5Cn%20*Please%20select%20a%20restaurant:*%22%7D%7D,%7B%22type%22:%22divider%22%7D,%7B%22type%22:%22section%22,%22text%22:%7B%22type%22:%22mrkdwn%22,%22text%22:%22*Ler%20Ros*%5Cn:star::star::star::star:%202082%20reviews%5Cn%20I%20would%20really%20recommend%20the%20%20Yum%20Koh%20Moo%20Yang%20-%20Spicy%20lime%20dressing%20and%20roasted%20quick%20marinated%20pork%20shoulder,%20basil%20leaves,%20chili%20&%20rice%20powder.%22%7D,%22accessory%22:%7B%22type%22:%22image%22,%22image_url%22:%22https://s3-media2.fl.yelpcdn.com/bphoto/DawwNigKJ2ckPeDeDM7jAg/o.jpg%22,%22alt_text%22:%22alt%20text%20for%20image%22%7D%7D,%7B%22type%22:%22divider%22%7D,%7B%22type%22:%22actions%22,%22elements%22:%5B%7B%22type%22:%22button%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Farmhouse%22,%22emoji%22:true%7D,%22value%22:%22click_me_123%22%7D,%7B%22type%22:%22button%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Kin%20Khao%22,%22emoji%22:true%7D,%22value%22:%22click_me_123%22%7D,%7B%22type%22:%22button%22,%22text%22:%7B%22type%22:%22plain_text%22,%22text%22:%22Ler%20Ros%22,%22emoji%22:true%7D,%22value%22:%22click_me_123%22%7D%5D%7D%5D) for all of your options on message formatting.  

My message formatting looks like this. 

```js
return {
  response_type: 'in_channel', // or `ephemeral` for private response
  blocks: [{
    "type": "image",
    "title": {
      "type": "plain_text",
      "text": images[index].title,
      "emoji": true
    },
    "image_url": images[index].link,
    "alt_text": images[index].title,
  }]
};
```

It's meant to be simple, it uses the Block Kit builder to include the image title and the URL of the image. Slack automatically renders the image and posts it in the channel you called it in. 

My full file for the g_image slash command is located on [GitHub](https://github.com/SambaDialloB/google_search/blob/master/src/g_image.js). It has everything you need to run the command, besides your Google API information.

## How does it look? <a name="how"></a>

Here's an example of me running my **g_image** command in Slack with Nimbella Commander. I type the name of our app, the name of the command, and then our parameters.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ofvrsth9sc1lfxaapf1h.gif)

I can choose the index of the image I want, so I don't get the same first image every time. A next step would be to allow randomization of results as well!

I'd love to see what type of serverless integrations you can create with Slack.