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.
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
).
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}"
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!"
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
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.
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.
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.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
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}'")
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