Slack Developer Kit for Hubot
Go to GitHub

Basic Usage

Most Hubots are designed to react to user input – a user makes a request, and Hubot responds, often after going out into the world to trigger some action (building your code, deploying to production, and so on). Many of the tasks you’d like to accomplish are already well documented in the official Hubot documentation. Nevertheless, we’ll cover the basics, as well as some interesting Slack-specific use cases.


Listening for a message

You can listen for messages in any channel that your Hubot has been invited into very simply, by using robot.hear with a RegExp to match against. Any message that matches the RegExp will trigger the function.

module.exports = (robot) ->

  # Any message that contains "badger" will trigger the following function
  robot.hear /badger/i, (res) ->

    # res.message is a SlackTextMessage instance that represents the incoming message Hubot just heard
    robot.logger.debug "Received message #{res.message.text}"

Hubot will only hear messages in converastions where it is a member. A human must invite Hubot into conversations (shortcut: /invite @username).


Messages directed to your Hubot

If you want to specifically listen for messages that mention your Hubot, use the robot.respond method. You can also be more specific using a RegExp (or don’t be more specific, /.*/ will match all messages).

module.exports = (robot) ->

  # Any message that contains "badger" and is directed at Hubot (in a DM or starting with its name)
  # will trigger the following function
  robot.respond /badger/i, (res) ->
    robot.logger.debug "Received message #{res.message.text}"

Sending a response

Responding to a message is straightforward, regardless of whether the message was sent to your Hubot specifically or to anyone in general.

module.exports = (robot) ->

  robot.hear /badger/i, (res) ->
    # Hubot sends a response to the same channel it heard the incoming message
    res.send "Yes, more badgers please!"

User and conversation mentions

When your Hubot hears a message, it might contain mentions of other users, channels, or groups. The text property is pretty and human-readable. But this isn’t great for scripting because usernames are deprecated, and display names and conversation names can change. What you really want is an ID; it’s stable to store and gives your Hubot an easy way to write mentions that have the user’s preferred display name.

Each incoming message has a mentions array that contains the ID and any other information known about the user or conversation that was mentioned.

module.exports = (robot) ->
  # A map of user IDs to scores
  thank_scores = {}

  robot.hear /thanks/i, (res) ->
    # filter mentions to just user mentions
    user_mentions = (mention for mention in res.message.mentions when mention.type is "user")

    # when there are user mentions...
    if user_mentions.length > 0
      response_text = ""

      # process each mention
      for { id } in user_mentions
        # increment the thank score
        thank_scores[id] = if thank_scores[id]? then (thank_scores[id] + 1) else 1
        # show the total score in the message with a properly formatted mention (uses display name)
        response_text += "<@#{id}> has been thanked #{thank_scores[id]} times!\n"

      # send the response
      res.send response_text

Using the Slack Web API

You can access the Slack Web API from your Hubot. Start by installing the Slack Developer Kit for Node.js package into your Hubot project:

npm install --save @slack/client

Next, modify your script to instatiate a WebClient object using the same token your Hubot used to connect.

# Import the Slack Developer Kit
{WebClient} = require "@slack/client"

module.exports = (robot) ->
  web = new WebClient robot.adapter.options.token

  # remainder of your script...

Finally, anytime you’d like to call a Web API method, call it like a method on web.

# Import the Slack Developer Kit
{WebClient} = require "@slack/client"

module.exports = (robot) ->
  web = new WebClient robot.adapter.options.token

  robot.hear /test/i, (res) ->
    web.api.test()
      .then () -> res.send "Your connection to the Slack API is working!"
      .catch (error) -> res.send "Your connection to the Slack API failed :("

You only have access to the Web API methods that your bot token is authorized to use. Depending on how you installed Hubot, the exact list of methods is either those checked for Custom Bots or App Bots in the bot methods table. If you have an App Bot and need to access a method only available to a Custom Bot, now might be the right time to switch. If you need access to a method that isn’t listed in the table at all, see Accessing more API methods and distribution.


Working with threads

Slack has the concept of threaded messages, which Hubot wasn’t directly designed to support. However, with a little bit of knowledge about the underlying messages, you can create new threads and send messages into a thread using Hubot.

module.exports = (robot) ->

  robot.hear /badger/i, (res) ->
    if res.message.thread_ts?
      # The incoming message was inside a thread, responding normally will continue the thread
      res.send "Did someone say BADGER?"
    else
      # The incoming message was not inside a thread, so lets respond by creating a new thread
      res.message.thread_ts = res.message.rawMessage.ts
      res.send "Slight digression, we need to talk about these BADGERS"

If you want to use the reply_broadcast feature of threads, you’ll have to use the Web API directly for the chat.postMessage method.


Message reactions

Of course, Slack is more than just text messages. Users can send emoji reactions to messages as well. Your Hubot can both listen for these from other users, and send reactions of its own. Here is a recipe to listen for emoji reactions and add the same reaction back to the same message.

{WebClient} = require "@slack/client"

module.exports = (robot) ->
  web = new WebClient robot.adapter.options.token

  robot.react (res) ->

    # res.message is a ReactionMessage instance that represents the reaction Hubot just heard
    if res.message.type == "added" and res.message.item.type == "message"

      # res.messsage.reaction is the emoji alias for the reaction Hubot just heard
      web.reactions.add
        name: res.message.reaction,
        channel: res.message.item.channel,
        timestamp: res.message.item.ts

When using robot.react as shown above, the res.message value is of type ReactionMessage. In addition to the normal message properties, this type has a few really helpful properties you might want to use in your script:

  • type: This is either "added" or "removed", depending on whether, you guessed it, the reaction was added or removed.
  • reaction: The name of the emoji reaction. For example, when adding a 👍 reaction, this value is "thumbsup".
  • item: This is either the message, the file, or the comment where this reaction took place.
  • item_user: The user who created the item. This value can be undefined if the item was created by a custom integration (not a Slack App).
  • event_ts: The timestamp of when this reaction message took place.

Presence changes

Each time a user changes from away to active, or vice-versa, Hubot can listen that event.

module.exports = (robot) ->

  robot.presenceChange (res) ->

    # res.message is a PresenceMessage instance that represents the presence change Hubot just heard
    names = (user.name for user in res.message.users).join ", "

    message = if res.message.presence is "away" then "Bye bye #{names}" else "Glad you are back #{names}"
    robot.logger.debug message

Send a message to a different channel

Responding right back to an incoming message is great, but sometimes you want send a message to a different channel. Hubot calls channels “rooms” and this adapter identifies rooms by channel ID, not by channel name. If your Hubot wants to send a message into another channel, it first needs to find that channel ID. You might get a channel ID from a previous message or you might use the Web API to translate a channel name to a channel ID. In the following example, we use the Web API to translate to default named channel to an ID and then store it in a variable. There’s a listener set up that can update that variable based on an incoming message. Then a new listener is used to send data into the current channel in the variable, no matter where the incoming message is received.

{WebClient} = require "@slack/client"

module.exports = (robot) ->
  web = new WebClient robot.adapter.options.token

  # When the script starts up, there is no notification room
  notification_room = undefined

  # Immediately, a request is made to the Slack Web API to translate a default channel name into an ID
  default_channel_name = "general"
  web.channels.list()
    .then (api_response) ->
      # List is searched for the channel with the right name, and the notification_room is updated
      room = api_response.channels.find (channel) -> channel.name is default_channel_name
      notification_room = room.id if room?

    # NOTE: for workspaces with a large number of channels, this result in a timeout error. Use pagination.
    .catch (error) -> robot.logger.error error.message

  # Any message that says "send updates here" will change the notification room
  robot.hear /send updates here/i, (res) ->
    notification_room = res.message.rawMessage.channel.id

  # Any message that says "my update" will cause Hubot to echo that message to the notification room
  robot.hear /my update/i, (res) ->
    if notification_room?
      robot.messageRoom(notification_room, "An update from: <@#{res.message.user.id}>: '#{res.message.text}'")

Text formatting and raw messages

When your Hubot receives a message, the adapter does its best to make the text easy to work with by formatting links and mentions. This formatting sometimes removes meaningful information from the text. If you want to access the unaltered text in the incoming message, you can use the rawText property. Similarly, if you need to access any other property of the incoming Slack message, use the rawMessage property.

module.exports = (robot) ->

  # listen to all incoming messages
  robot.hear /.*/, (res) ->
    # find URLs in the rawText
    urls = res.message.rawText.match /https?:\/\/[^\|>\s]+/gi

    # log each link found
    robot.logger.debug ("link shared: #{url}" for url in urls).join("\n") if urls