Creating Slack Slash Commands With Azure Functions

One of the nice features of Slack is how easy they make it to add custom slash commands simply by providing an HTTP endpoint for them to call. This is the kind of scenario where a "serverless" architecture shines, allowing you to create these HTTP endpoints without having to maintain any actual infrastructure for it. In this post I'll show how easy it is to write a custom Slack slash command that is backed by Azure Functions. The command will allow the user to say /logo {query} to look up the logo for a given company.

Create The Function

To start off we'll implement this function in C#. There's not much code, so I'll just include it all at once:

#r "Newtonsoft.Json"
using System.Net;  
using System.Net.Http.Formatting;  
using System.Collections.Generic;  
using Newtonsoft.Json;

public class Company  
{
    public string Logo { get; set; }
    public string Name { get; set; }
}

public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)  
{
    var text = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "text", true) == 0)
        .Value;

    using (var client = new HttpClient()) 
    {
        var json = await client.GetStringAsync($"https://autocomplete.clearbit.com/v1/companies/suggest?query={Uri.EscapeUriString(text)}");
        var companies = JsonConvert.DeserializeObject<IList<Company>>(json);
        var company = companies.First();

        var output = new 
        {
            text = $"Here's the logo for *{company.Name}*:",
            attachments = new[] 
            {
                new { image_url = company.Logo, text = company.Logo }
            }
        };

        return req.CreateResponse(HttpStatusCode.OK, output, JsonMediaTypeFormatter.DefaultMediaType);
    }
}

Using the string passed through from Slack we query Clearbit's Autocomplete API, pick the first company returned, and construct a message in the format Slack expects it, and returns that as a JSON response. We also get to use all the nice things we're used to in C# development like async/await, HttpClient, and Json.NET.

Finally, we'll need to update function.json to set the HTTP input and output:

{
  "bindings": [
    {
      "type": "httpTrigger",
      "name": "req",
      "authLevel": "anonymous",
      "direction": "in"
    },
    {
      "type": "http",
      "name": "res",
      "direction": "out"
    }
  ],
  "disabled": false
}

That's everything needed for the function, which should now be fully operational.

Create the Slash Command

In your Slack settings, choose to create a new slash command, set it to use a GET, and point it at your Azure Function:

Slash Command Config

Save that and you should be good to go! Let's try it out:

/logo olo

Olo Logo

/logo microsoft

Microsoft Logo

Easy!

Make That Function More Functional

For extra credit, let's recreate this function in F# because F# is awesome. First we'll need a project.json file to bring in some dependencies:

{
    "frameworks": {
        "net46": {
            "dependencies": {
                "FSharp.Data": "2.3.2",
                "Newtonsoft.Json": "9.0.1"
            }
        }
    }
}

This function can use the same function.json settings as the C# version. Finally, the code:

open System.Net  
open System.Net.Http.Formatting  
open System.Text  
open FSharp.Data  
open Newtonsoft.Json  

type Company = { Name: string; Logo: string; }  
type Attachment = { image_url: string; text: string; }  
type Message = { text: string; attachments: Attachment[]; }

let getSearchQuery (req: HttpRequestMessage) =  
    req.GetQueryNameValuePairs() 
    |> Seq.find (fun pair -> pair.Key.ToLowerInvariant() = "text")
    |> fun pair -> pair.Value

let getCompany query =  
    Uri.EscapeUriString query
    |> sprintf "https://autocomplete.clearbit.com/v1/companies/suggest?query=%s"
    |> Http.RequestString
    |> JsonConvert.DeserializeObject<Company[]>
    |> Array.head

let Run (req: HttpRequestMessage) =  
    getSearchQuery req
    |> getCompany
    |> fun company -> { text = (sprintf "Here's the logo for *%s*:" company.Name)
                        attachments = [| { image_url = company.Logo; text = company.Logo } |] }
    |> JsonConvert.SerializeObject
    |> fun json -> new HttpResponseMessage(HttpStatusCode.OK, Content = new StringContent(json, Encoding.UTF8, "application/json"))    

Now our function is nice and functional.

comments powered by Disqus
Navigation