facebook
Aaron Lara
Developer Relations Lead at Genuitec. I like my software like I like my coffee: hot and delicious (ok, I suck at this...). Ah! I'm a gamer, so if you want to play something in steam just let me know :D
Contributions by Oscar Grajeda
Posted on Oct 6th 2015

Many teams, including ours, now use Slack for team collaboration. With Slack, you can communicate with other team members in an organized public chat room (called a channel), a private group, or send a direct message. The ability to archive these messages, and then search for specific conversations or files later, makes for a powerful communication tool. In addition, Slack integrates with a number of third-party services for added convenience.

Slack includes a variety of handy slash commands you can use in the message input box (e.g., /hangout, /away, /invite, /leave, etc.). These built-in commands are great, but did you know you can create your own custom Slack commands with JAX-RS that get data, process it and respond to the issuer? Cool, right? The following tutorial guides you through the process of creating your own Slack command handler using JAX-RS.

Overview

There are three principle parts to making a Slack command handler:
  • Register and set up the command in Slack.
  • Receive the Slack command into your Java backend.
  • Send a message back to the appropriate Slack channel, group or direct message.
    Note:  Slack commands provide more flexibility than Webhooks by supporting channels, private groups and direct messages.

The Slack API provides the ability to create custom Slack commands to trigger code reactions in external handlers (web services). The data you need to send to the handler (arguments) is sent via POST by Slack when you issue a command. A response message is sent back only to the user who issued the command. For more information click here.

When the Slack command is issued it includes key information including the channel, user ID, token of command, additional text, etc. You can grab that information to process in your handler, then use the token provided to authenticate and respond back to Slack.

So the clean way to integrate your customized command is to receive a command and then fire back a message once processed by your own code full of awesomeness!

Give it a Try!

I have included a sample project to demonstrate the implementation of custom Slack commands. Use the sample project along with the steps below to create a slash command that receives an id, notifies the user the request is being processed, and then returns a detailed message for the id.

Setting up Slack Commands

To activate the request in Slack, configure the command on the Slack Commands page: https://<TeamDomain>.slack.com/services/new/slash-commands.

From this page, configure the URL to request when the slash command is executed and the method used to send the payload (in this case we will use POST). In addition, a token is generated and sent in the payload, you can use the token to validate if the message came from your team.

Receiving Slack Commands

You can create a JAX-RS web service using Eclipse, or better yet, using MyEclipse. Ours is pretty simple: our Slack integration class has the @Path notation which should match the path you configured when setting up the Slack command.
@Path("/integrationx")
public class IntegrationXServices {
Basically it is a POST request to our server, so we use JAX-RS to receive and automatically process the data.
@POST
@Path("/command")
@Produces(MediaType.TEXT_PLAIN)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response command(@FormParam("token") String token, @FormParam("text") String text, @FormParam("channel_id") String channel) throws Exception { 
// DO SOME MAGIC

The token can be used to ensure the request is coming from a valid channel in Slack, so you can validate the request like this:

if (!CMD_TOKEN.equals(token))
   return Response.status(404).build();

Use the channel as a magic key to respond back to the channel (public channel, private channel or even direct message channel) that requested the service.

The text argument is the actual text written after the command in the chat input box.
`/command <text sent as argument>`

Use the text argument that corresponds to your command. In this example we are expecting the command to have “id” arguments separated by commas:
`/command id1,id2,id3.`

We want to process the IDs separately, for that we will “detokenize” the text to give us a set of strings:

Set<String> ids = Detokenizer.tokensFor(null, text);

Now we call a job to do whatever cool process we want to do with those IDs:

SlackAPIs.timer.schedule(new IntegrationXLookup(channel, ids), 10);

We also want to immediately notify the user that “we’re on it”.

return Response.ok("Ok, we're on it. Looking for "+ids).build();

The job that actually does the work of processing the data is (in this case) just extending a TimerTask:

class IntegrationXLookup extends TimerTask {

In the run() method we will do some cool stuff and then respond with the processed data:

@Override
public void run() {
// Super cool code here 
&nbsp; &nbsp; &nbsp; SlackSender.sendTo(channel, "Recording "+ids.size()+" records", "My Integration", "https://www.genuitec.com/ext/bug.png", attachments);
}

Sending Results to Slack

At this point we have already processed the data and we just want to send the results back to the channel. In this case, we use channel_id and our authentication token.

Note: This is not the same token generated when setting up the Slack command, you need to generate your own token at https://api.slack.com/web.

Now we are just using the fields we already know and sending the response back as a POST method to the Slack API method chat.postMessage. Check out how we build it:

public class SlackSender {

   public static void sendTo(String channel, String text, String username, String icon_url, JSONArray attachments) throws Exception {
      HttpPost postMethod = new HttpPost("https://slack.com/api/chat.postMessage");
      HttpClient client = HttpClientBuilder.create().build();
      try {
         List<NameValuePair> urlParameters = new ArrayList<NameValuePair>();
         urlParameters.add(new BasicNameValuePair("token", TOKEN));
         urlParameters.add(new BasicNameValuePair("channel", channel));
         urlParameters.add(new BasicNameValuePair("text", text));
         urlParameters.add(new BasicNameValuePair("as_user", "false"));
         urlParameters.add(new BasicNameValuePair("username", username));
         urlParameters.add(new BasicNameValuePair("parse", "full"));
         if (icon_url != null)
            urlParameters.add(new BasicNameValuePair("icon_url", icon_url));
         if (attachments != null)
            urlParameters.add(new BasicNameValuePair("attachments", attachments.toString()));
         urlParameters.add(new BasicNameValuePair("link_names", "1"));
         postMethod.setEntity(new UrlEncodedFormEntity(urlParameters));
         HttpResponse response = client.execute(postMethod);

To understand how to do really cool stuff with the response, read on!

Making Messages Sexy

OK, I lied about having just 3 parts…Slack is great because you can include well formatted data (aka attachments) along with the message you are sending back to the Slack channel. For this we will use Slack Attachments.

You may have already noticed in the previous code that we added a field for the attachments:

if (attachments != null)
   urlParameters.add(new BasicNameValuePair("attachments", attachments.toString()));

Now we can get back to the Job class and add the following code to the run() method before the call to SlackSender.sendTo.

@Override
public void run() {
   JSONArray attachments = new JSONArray();
   for (String id : ids) {
      JSONObject attachment = new JSONObject();
      attachment.accumulate("title", "Found record for ["+id+"]");
      attachment.accumulate("title_link", "http://www.google.com");
      attachment.accumulate("color", "warning");
      attachment.accumulate("text", "This is my voice.\nIt is a good voice.\nAnd will get cropped by Slack if very long (700 chars)");
      attachment.accumulate("author_name", "I am Bot");
      attachment.accumulate("author_icon", "http://www.genuitec.com/ext/slack-user.png");
      JSONArray fields = new JSONArray();
      fields.add(field("Opened", "Yesterday"));
      fields.add(field("Assigned To", "Nobody"));
      fields.add(field("Severity", "Wicked Bad"));
      attachment.accumulate("fields", fields);
   }

What’s awesome is you can send multiple blocks of formatted text. For example, you may want to provide a service to find Work Items in Kanban that are assigned to you.

You can format each one with nice style. Even the “text” field inside the attachment can actually have Slack formatting included in it. Awesome, right? RIGHT?!

A little aside that if you want to provide formatting within fields themselves you have to inform Slack about it. We don’t bother on our integrations—don’t want to make the messages too distracting. For more about formatting messages, read here.

Acknowledgements

I want to offer a special thanks to Oscar Grajeda and Tim Webb  for their help in bringing you this information.