Create a custom Slack bot
Learn how to make and interact with custom Slack bots to increase your productivity.
Slack is an increasingly popular tool for businesses and teams who need to communicate instantly. While it may – in some cases – be considered a daily disruption, it also has a great automation potential, offering dozens of integrations to keep everything in one place, and it is slowly superseding emails.
There are many ways to integrate your systems with Slack; the platform even provides a branded bot that lets you deliver reminders, or messages across your digital workspace. And for even more collaboration enhancements, try these cloud storage options.
Slack offers various entities that could be considered 'bots':
- webhooks, which allow to post messages from other apps into Slack, but are a one-way form of communication,
- apps, for advances integrations (with other apps),
- bot users, more on them shortly.
In this tutorial we'll be looking particularly at 'bot users', how to program them (check out our guide to the best code editors to make your life easier) and make them fit your needs. This presupposes you have access to a Slack space where you can add app integrations. If you are not already part of one, they are free to create.
In what follows, we build a NodeJS app to post-to and respond to particular messages in a Slack channel. We use the 'slackbots' module, which is a wrapper for the Slack Real-Time Messaging API.
Also interested in making a website? Choose a brilliant website builder and top web hosting service to go with it.
01. Code setup
This tutorial assumes you have node installed. If not, do so now. Then download the assets you'll need. We have provided you with a starting point and framework for the tutorial.
Get the Creative Bloq Newsletter
Daily design news, reviews, how-tos and more, as picked by the editors.
Throughout, we make use of modules such as 'slackbots' and 'node-slack-upload'. They can be obtained by running the install command.
npm install
02. Slack setup
We are using the "Bot user" integration for Slack. To do so, we need to get a token by going to 'https://<youSlackworkspace>.slack.com/apps/A0F7YS25R-bots' and click "Add Configuration".
Choose a username for your bot (we can override this programmatically at a later stage), and confirm.
Invite the bot to the desired channel.
By creating a bot user you will get a token in the following format:
xoxb-000000-000000-x0x0xxXxX0XXxx0x
Copy the token for the next step.
03. Environment variables
We make use of environment variables (a '.env' file) to avoid hard-coding and revealing secret tokens and keys, like the Slack token we've generated, and the channel name in your private Slack workspace.
Go ahead and fill in the '.env' file with your token, and the name of the channel to which you've invited the bot user.
SLACK_TOKEN=xoxb-<YourToken>
SLACK_CHANNEL=<channel-name>
04. Bot parameters
This next step takes us to 2 files: 'index.js', which we'll have a brief look at, and 'bin/lib/bot.js', where most of our development takes place. In the index file, we instantiate our bot by giving it a name, which is 'WDMBot'.
In 'bot.js' we control the parameters of each instance with name, token, etc.
//index.js
const bot = require('./bin/lib/bot').init
('WDMBot');
//bot.js
const botParams = {
icon_emoji: ':robot_face:',
as_user: false
};
let bot;
function initBot(botName) {
bot = new slackbot({
token: process.env.SLACK_TOKEN,
name: botName
});
}
05. Post to channel
Have a look at the 'sendMessage' function. We use the 'postTo' method. This will handle posting to any type of channel, public or private. If you only want to post to private channels you could use 'postToGroup' instead (or 'postToChannel' for a public one). To send our first message, we can add code in 'initBot'.
function initBot(botName) {
bot = /* see above */
sendMessage('I'm here!');
}
// Now run 'npm start'
06. Custom botParams
You should have noticed a message from WDMBot appear in your channel. It is worth noting that in 'botParams', 'as_user' is set to false, which lets us override the name and image. If set to true, it will use the name and image you set when getting the token.
You could change the bot emoji to an image like so:
const botParams = {
icon_url: 'https://pbs.twimg.com/
profile_images/976112552081010688/
WLlQvj8D_400x400.jpg',
as_user: false
};
07. Channel events
Posting messages is useful, but to make the bot more interactive, we need to be able to identify posts from other users in the channel. Let's listen to the message event, and then see what happens when we type into to channel. We should see different message types being logged, like 'user_typing' or 'message'.
function initBot(botName) {
bot = /* see above */
bot.on('message', data => {
console.log(data);
});
}
08. Respond to incoming messages
Next, we want to reply to incoming messages of the type 'message', and maybe to a specific phrase or keyword, to avoid replying to absolutely everything. We make sure to compare lowercase strings if we want to match an exact phrase. We could also see if a message 'includes()' a particular word.
bot.on('message', data => {
if(data.type === 'message') {
if(data.text.toLowerCase() === 'where
are you?') {
sendMessage('I'm here!');
}
}
});
09. Restrict to "human" users
Messages sent by bot users have various properties such as a subtype of 'bot_message' and a bot_id. You might want to restrict replying to only human-posted messages to avoid infinite loops of bots replying to themselves or each other, if their response includes one of the keywords you're listening for.
bot.on('message', data => {
if(data.type === 'message' && data.
subtype!== 'bot_message') {
if(data.text.toLowerCase().
includes('hello')) {
sendMessage('Hello, I'm a bot!');
}
}
});
10. Personalised response
To give a more personalised response, you can leverage the user id of the message you're replying to. Slack will automatically convert an id to the user name when enclosed in the tags '<@>'. Identifying who you are replying to can be useful, especially if multiple channel members are interacting with your bot simultaneously.
bot.on('message', data => {
if(data.type === 'message' && data.
subtype!== 'bot_message') {
if(data.text.toLowerCase().
includes('hello')) {
sendMessage('Hello <@${data.user}
> I'm a bot!');
}
}
});
11. Update responses
Bots can also edit their responses. Only their own, though. So if you were hoping for a typo-spotting bot that would correct your messages automatically when it spots a mistake, that's not possible with the current setup.
To update the message, we define a new function, and a global Boolean that we'll use in our demo.
let changeReply = false;
function updateMessage(messageParams) {
bot.updateMessage(messageParams.channel,
messageParams.ts, messageParams.text,
botParams);
}
12. Change the message
Let's try to update the text the bot sends us. In this case, on a message event, we need to reply to an incoming bot message, so we'll match that condition for the update, and we also use the timestamp of the original message to be updated. That is so Slack can identify which message to update, in case others get posted in-between.
if(data.type === 'message' && data.subtype!==
'bot_message' && data.text) {
if(data.text.includes('update')) {
sendMessage('I'll update in 5
seconds');
changeReply = true;
}
}
if(data.type === 'message' && data.subtype ===
'bot_message' && changeReply) {
changeReply = false;
setTimeout(() => {
updateMessage({ channel: data.channel,
ts: data.ts, text: 'I have updated'});
}, 5000);
}
13. Ephemeral messages
Ephemeral messages are, as the name might suggest, temporary. They are also only visible to one user and can be deleted by them. Those types of messages might be useful as a tip or reminder that doesn't need to stay permanently.
if(data.text.toLowerCase().includes(' hr ')) {
ephemeralMessage({
channel: data.channel,
user: data.user,
text: 'If you need to contact HR,
their email is hr@mycompany.com'
});
}
//The function we're calling
function ephemeralMessage(messageParams) {
bot.postEphemeral(messageParams.channel,
messageParams.user, messageParams.text,
botParams);
}
14. User lookup
Different methods will take slightly different user parameter (either ID or name, which is different from the display_name and real_name). However, only the user id is available on message events. We can therefore implement a user name lookup by getting all users and matching the ID.
async function getUserName(userID) {
return await bot.getUsers()
.then(data => {
let member = data.members.find
(user => {
return user.id === userID;
});
return member.name;
})
.catch(err => console.log(err));
}
15. Send direct messages
With the new user lookup, we can now send direct messages to a user, when ephemeral messages just won't do. Note that direct messages are considered to be a new/different channel, with a different ID than the original channel. You could also implement a channel lookup in the same way as the user we've done previously.
//In the message event
if(data.text.toLowerCase().includes('bot')) {
sendDM({
user: data.user,
text: 'How can I help?'
});
}
//The function we're calling
async function sendDM(messageParams) {
let user = await getUserName
(messageParams.user);
return bot.postMessageToUser(user, message
Params.text, botParams, (err, data) => {
console.log(err)
});
}
16. Respond with an image
Bot users also have permissions to upload files and images to a channel. This functionality is not covered by 'slackbots' though, so we have to instantiate a new uploader, as demonstrated below. Also prepare an 'assets' folder at your project root, with some images in it.
Let's prepare a call to 'sendImage()', defined in the next step.
//in initBot()
uploader = new slackUpload(process.env.
SLACK_TOKEN);
//in the message event
if(data.text.includes('image')) {
if(!data.upload) {
/*In this case, there is no message
subtype,
so we check that it's not triggered by a
previous upload message*/
let image = data.text.split(' ')[1];
sendImage('This is the image you
wanted', image);
}
}
17. The file upload function
We upload images using the uploader and the FileSystem (fs) module. Provided that a user's message is in the format "image <imagename.extension>", and such a file exists in the 'assets' folder, the image will be read and uploaded. If not, we send back a regular message (it could even be an ephemeral one).
function sendImage(message, image) {
uploader.uploadFile({
file: fs.createReadStream(path.join
(__dirname, '../../assets/${image}')),
mimetype: 'image/*',
filetype: '*',
title: image,
initialComment: message,
channels: channel
}, (err, data) => {
if(err) {
sendMessage('Sorry I can't find
${image}');
}
});
}
18. Post to multiple channels
You can post to multiple channels with the same bot user, as long as it is a member of each channel where you are expecting a response. Let's create a 'postToAll' function and update the environment variables to have channel names as comma-separated values.
const channels = process.env.SLACK_CHANNEL.
split(',');
const channel = channels[0];
function postToAll(message) {
channels.forEach( channel => {
bot.postTo(channel, message, botParams);
});
}
19. Split channels
Occasionally, you might want to use channels for debugging, or respond to events differently with the same bot in different channels. It is up to you to workout your channel naming convention. We will assume for the following example that SLACK_CHANNEL=wdm-tutorial,wdm-tutorial-debug.
function splitMessages(message, debugMessage) {
channels.forEach( channel => {
const msg = channel.includes('debug')
?debugMessage:message;
bot.postTo(channel, msg, botParams);
});
}
20. Dictionary of responses
We have been hard-coding responses directly in the message. Going forward, to make things more manageable, you might want to store triggers and responses, either in a database or JSON format, and switch between them depending on the conditions met.
//Example response item
{
input: ' hr ',
inputMatch: 'include',
response: 'If you need to contact HR,
their email is hr@mycompany.com',
responseType: 'ephemeral'
}
21. Further resources
There are a few other useful properties available in the Slack API. Hopefully, this tutorial will have given an overview of what's possible for all your bot needs. Further resources can be found by reading the 'slackbots' documentation, or the full Slack API documentation.
This article was originally published in issue 289 of creative web design magazine Web Designer. Buy issue 289 here or subscribe to Web Designer here.
Related articles:
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1