Loading...

How to make a Slack Bot in Java

how-to jbot project
Ram Patra Published on January 31, 2020
Image placeholder

We will be using JBot––a tiny Java Framework––to develop a Slack Bot within minutes.

Table of Contents

  1. Getting started
  2. Basic Usage
  3. Building a Slack Integration with JBot
  4. Usage
  5. Deploy to the cloud
  6. Documentation History

Getting started

Running your SlackBot is just 4 easy steps:

  1. Clone this project $ git clone [email protected]:ramswaroop/jbot.git.
  2. Create a slack bot and get your slack token.
  3. Paste the token in application.properties file.
  4. Run the example application by running JBotApplication in your IDE or via commandline:
     $ cd jbot
     $ mvn clean install
     $ cd jbot-example
     $ mvn spring-boot:run
    

You can now start talking with your bot ;)

Basic Usage

The main function of a Bot is to receive and reply messages. With JBot, receiving messages is as easy as just writing a simple @Controller and replying to it by calling the reply() method as shown below:

@Controller(events = EventType.MESSAGE)
public void onReceiveDM(WebSocketSession session, Event event) {
    reply(session, event, "Hi, I am a Slack Bot!");
}

All the code for your bot goes in SlackBot class which extends Bot from the core package. You can have as many bots as you want, just make the class extend Bot class and it gets all the powers of a Slack Bot. Though it is recommended to have separate JBot instances for different bots.

Building a Slack Integration with JBot

You can integrate your services into Slack by any of the following ways:

And JBot currently supports:

Bots interact with Slack through RTM API or technically via Web Sockets. Slash Commands are nothing but GET and POST calls to your app. Finally, Webhooks can be of two types, Incoming and Outgoing. Incoming webhooks is where you POST data from outside (i.e, your app) to Slack and Outgoing webhooks is where Slack POST data to an endpoint you specify.

Setting up your app

You need to first paste your tokens/urls in application.properties file:

slackBotToken=xoxb-50014434-slacktokenx29U9X1bQ
slashCommandToken=X73Fv3tokenx242CdpEq
slackIncomingWebhookUrl=https://hooks.slack.com/services/T02WEBHOOKURLV7oOYvPiHL7y6

You can directly use jbot-example or use jbot as a dependency. To make a

  • Slack Bot ⇒ Extend Bot class.
  • Slash Command Handler ⇒ Annotate your class with Spring’s @Controller and have a method with the required @RequestMapping path receiving a set of request params as shown in the sample.
  • Slack Incoming Webhook ⇒ Just make a POST call with RichMessage whenever you want to update your Slack users about something.
  • Slack Outgoing Webhook ⇒ Same as Slash Command Handler.

Since JBot 4.0.0, there is a new property which helps turn specific services on/off. You can set the property in application.properties file:

spring.profiles.active=slack,facebook

To use Jbot for Slack only, remove “facebook” from the profiles. Note: You must have @Profile defined in your Slack bot classes. See SlackBot in jbot-example.

Receiving Messages

For Bots, you receive a message as Event. For almost all actions Slack fires a relevant event for it. Unfortunately, it does not fire appropriate events when someone directly messages the bot (direct message) or mentions the bot on a channel (like @bot). It just fires an event of type message for all the messages (directly to bot and to channels where bot is a member) sent.

But guess what, you’re at the right place now, JBot handles that for you. It supports three extra events EventType.DIRECT_MESSAGE, EventType.DIRECT_MENTION and EventType.ACK in addition to all the currently supported Slack events. The first two events are self-explanatory, the EventType.ACK event is nothing but an acknowledgement event which acknowledges the delivery of a previously sent message.

To receive and parse slack bot events you just need to have this:

@Controller(events = {EventType.DIRECT_MENTION, EventType.DIRECT_MESSAGE})
public void onReceiveDM(WebSocketSession session, Event event) {
    if (event.getText().contains("hi")) {
        reply(session, event, "Hi, I am " + slackService.getCurrentUser().getName());
    }
}

What you’re doing here is annotating a method with @Controller annotation and passing an array events to that annotation which you want to listen to. By default your controller will listen to EventType.MESSAGE events if you do not specify any events explicitly.

You can also add regular expressions to your @Controller annotation like:

@Controller(events = EventType.MESSAGE, pattern = "^([a-z ]{2})(\\d+)([a-z ]{2})$")
public void onReceiveMessage(WebSocketSession session, Event event, Matcher matcher) {
    reply(session, event, new Message("First group: " + matcher.group(0) + "\n" +
            "Second group: " + matcher.group(1) + "\n" +
            "Third group: " + matcher.group(2) + "\n" +
            "Fourth group: " + matcher.group(3)));
}

You can optionally have a matcher as a formal parameter in the method if you want to work on the values sent by the user. But do keep the order of parameters as shown above.

In Slash Commands, you receive a GET or POST request as below:

token=gIkuvaNzQIHg97ATvDxqgjtO
team_id=T0001
team_domain=example
channel_id=C2147483705
channel_name=test
user_id=U2147483697
user_name=Steve
command=/weather
text=94070
response_url=https://hooks.slack.com/commands/1234/5678

If you have configured for POST requests, data will be sent to your URL with a content-type header set as application/x-www-form-urlencoded. If you’ve chosen to have your slash command’s URL receive invocations as a GET request, no explicit content-type header will be set.

NOTE: The URL you provide must be a HTTPS URL with a valid, verifiable SSL certificate.

In Incoming Webhooks, your application POST data and do not receive any data apart from the acknowledgement for your sent data. You send data as RichMessage to Slack Webhook URL.

In Outgoing Webhooks, you receive a POST request from Slack like below:

token=mbxmjpceetMUz2hfecqM31KC
team_id=T0001
team_domain=example
channel_id=C2147483705
channel_name=test
timestamp=1355517523.000005
user_id=U2147483697
user_name=Steve
text=googlebot: What is the air-speed velocity of an unladen swallow?
trigger_word=googlebot:

Please note that the content of message attachments will not be included in the outgoing POST data in case of Outgoing Webhooks.

Sending Messages

In Bots, you can use the reply() method defined in Bot class to send messages to Slack. You just need to set the text you want to send in Message and everything else will be taken care by JBot. But you can set other fields if you want such as id in the message.

Here is an example:

@Controller(events = EventType.MESSAGE)
public void onReceiveMessage(WebSocketSession session, Event event) {
    reply(session, event, "Hi, this is a message!");
}

Under the hood the message sent is nothing but a json like below:

{
    "id": 1,
    "type": "message",
    "channel": "C024BE91L",
    "text": "Hi, this is a message!"
}

For Slash Commands and Incoming Webhooks, you can send messages as RichMessage. Just keep in mind to encode it before sending by just calling the encodedMessage() method. Below is an example:

@RequestMapping(value = "/slash-command",
        method = RequestMethod.POST,
        consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public RichMessage onReceiveSlashCommand(@RequestParam("token") String token,
                                         @RequestParam("team_id") String teamId,
                                         @RequestParam("team_domain") String teamDomain,
                                         @RequestParam("channel_id") String channelId,
                                         @RequestParam("channel_name") String channelName,
                                         @RequestParam("user_id") String userId,
                                         @RequestParam("user_name") String userName,
                                         @RequestParam("command") String command,
                                         @RequestParam("text") String text,
                                         @RequestParam("response_url") String responseUrl) {
    // validate token
    if (!token.equals(slackToken)) {
        return new RichMessage("Sorry! You're not lucky enough to use our slack command.");
    }

    /** build response */
    RichMessage richMessage = new RichMessage("The is Slash Commander!");
    richMessage.setResponseType("in_channel");
    // set attachments
    Attachment[] attachments = new Attachment[1];
    attachments[0] = new Attachment();
    attachments[0].setText("I will perform all tasks for you.");
    richMessage.setAttachments(attachments);
    return richMessage.encodedMessage(); // don't forget to send the encoded message to Slack
}

Points to Note:

Conversations

This is a differentiating feature of JBot, with this you can literally talk to your bot and have a conversation. See below for an example as to how your bot sets up a meeting for your team by asking some simple questions one after the other.

Conversation feature of JBot

/**
 * Conversation feature of JBot. This method is the starting point of the conversation (as it
 * calls {@link Bot#startConversation(Event, String)} within it. You can chain methods which will be invoked
 * one after the other leading to a conversation. You can chain methods with {@link Controller#next()} by
 * specifying the method name to chain with.
 *
 * @param session
 * @param event
 */
@Controller(pattern = "(setup meeting)", next = "confirmTiming")
public void setupMeeting(WebSocketSession session, Event event) {
    startConversation(event, "confirmTiming");   // start conversation
    reply(session, event, new Message("Cool! At what time (ex. 15:30) do you want me to set up the meeting?"));
}

You can start a conversation by calling startConversation(event, nextMethodName) within your controller. You can pass the event and the name of the next controller method.

/**
 * This method is chained with {@link SlackBot#setupMeeting(WebSocketSession, Event)}.
 *
 * @param session
 * @param event
 */
@Controller(next = "askTimeForMeeting")
public void confirmTiming(WebSocketSession session, Event event) {
    reply(session, event, new Message("Your meeting is set at " + event.getText() +
            ". Would you like to repeat it tomorrow?"));
    nextConversation(event);    // jump to next question in conversation
}

This is your next method in the conversation. After your desired work is done, do not forget to call nextConversation(event) to jump to the next method. You can specify the next method to call in next attribute of Controller annotation.

/**
 * This method is chained with {@link SlackBot#confirmTiming(WebSocketSession, Event)}.
 *
 * @param session
 * @param event
 */
@Controller(next = "askWhetherToRepeat")
public void askTimeForMeeting(WebSocketSession session, Event event) {
    if (event.getText().contains("yes")) {
        reply(session, event, new Message("Okay. Would you like me to set a reminder for you?"));
        nextConversation(event);    // jump to next question in conversation  
    } else {
        reply(session, event, new Message("No problem. You can always schedule one with 'setup meeting' command."));
        stopConversation(event);    // stop conversation only if user says no
    }
}

/**
 * This method is chained with {@link SlackBot#askTimeForMeeting(WebSocketSession, Event)}.
 *
 * @param session
 * @param event
 */
@Controller
public void askWhetherToRepeat(WebSocketSession session, Event event) {
    if (event.getText().contains("yes")) {
        reply(session, event, new Message("Great! I will remind you tomorrow before the meeting."));
    } else {
        reply(session, event, new Message("Oh! my boss is smart enough to remind himself :)"));
    }
    stopConversation(event);    // stop conversation
}

NOTE:

  • Only the first method in a conversation can define a pattern. pattern attribute in Controller annotation has no effect for rest of the methods in a conversation.
  • The first method in the conversation need not call nextConversation(event) but rest of the methods do need to.
  • next attribute in @Controller should have the name of the next method in the conversation that needs to be invoked.
  • To end the conversation, call stopConversation(event) inside your controller method.

Usage

You can directly clone this project and use jbot-example or you can include it as a maven/gradle dependency in your project.

Maven

<dependency>
    <groupId>me.ramswaroop.jbot</groupId>
    <artifactId>jbot</artifactId>
    <version>4.1.0</version>
</dependency>

Gradle

dependencies {
    compile("me.ramswaroop.jbot:jbot:4.1.0")
}

NOTE: When you include jbot as a dependency please make sure to include me.ramswaroop.jbot package for auto-scan. For example, you can specify scanBasePackages in @SpringBootApplication or @ComponentScan. See jbot-example to learn more.

Deploy to the Cloud

JBot is Heroku ready. To deploy, you need to perform the below simple steps:

  1. Clone this project $ git clone [email protected]:ramswaroop/jbot.git and $ cd jbot.
  2. Get your slack bot token or slash command token or incoming webhook url.
  3. Paste the above tokens/urls in application.properties file.
  4. Download Toolbelt for your system.
  5. $ heroku login - Login to Heroku.
  6. $ heroku create - Create an app on Heroku.
  7. $ git push heroku master - Push your code to Heroku.
  8. $ heroku ps:scale web=1 - Start your application.

You can now start talking with your Bot, send commands to your Slash Command or play with Incoming Webhooks ;)

Documentation History

Ram Patra Published on January 31, 2020
Image placeholder

Keep reading

If you liked this article, you may like these as well

August 20, 2019 Nested Classes in Java

A class inside another class is called a Nested Class. In other words, as a class has member variables and member methods it can also have member classes.

gadget unboxing hifi November 5, 2021 Unboxing of KEF Q350 Speakers

I always wanted to have a nice music system in my house since I was a kid. However, I now finally got the chance to build my own HiFi audio system. After some research and listening to different speakers at my local HiFi stores, I shortlisted the following speakers based on my budget:

June 2, 2019 Overloading in Java

Reusing the same method name in the same class or subclass but with different arguments(and optionally, a different return type) is called Method Overloading in Java.