hubot-slack
Version:
A Slack adapter for hubot
302 lines (221 loc) • 12 kB
Markdown
layout: page
title: Basic Usage
permalink: /basic_usage
order: 4
headings:
- title: Listening for a message
- title: Messages directed to your Hubot
- title: Sending a response
- title: User and conversation mentions
- title: Using the Slack Web API
- title: Working with threads
- title: Message reactions
- title: Presence changes
- title: Send a message to a different channel
- title: Text formatting and raw messages
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](https://hubot.github.com/docs/).
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.
```coffeescript
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).
```coffeescript
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.
```coffeescript
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](https://api.slack.com/changelog/2017-09-the-one-about-usernames), 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.
```coffeescript
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](https://api.slack.com/web_api) from your Hubot. Start by installing the
[Slack Developer Kit for Node.js](https://slackapi.github.io/node-slack-sdk/) 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.
```coffeescript
# 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`.
```coffeescript
# 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]({{ site.baseurl }}{% link index.md %}#{{ "Getting a Slack token" | slugify }}), the
exact list of methods is either those checked for Custom Bots or App Bots in the
[bot methods table](https://api.slack.com/bot-users#bot_methods). 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]({{ site.baseurl }}{% link _pages/advanced_usage.md %}#{{ "Accessing more API methods and distribution" | slugify }}).
--------
## Working with threads
Slack has the concept of [threaded messages](https://api.slack.com/docs/message-threading), 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.
```coffeescript
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](#{{"Using the Slack Web API" | slugify}}) for the `chat.postMessage` method.
--------
## Message reactions
Of course, Slack is more than just text messages. Users can send
[emoji reactions](https://get.slack.help/hc/en-us/articles/206870317-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.
```coffeescript
{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.
```coffeescript
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.
```coffeescript
{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.
```coffeescript
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
```