Skip to main content

Create a custom Slack bot

Create a custom Slack bot
(Image credit: Web Designer)

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.

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.

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.

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

Create a custom Slack bot: Slack setup

Give your bot a username and invite it to your channel

(Image credit: Web Designer)

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

Create a custom Slack bot: Post to channel

Now you can get your bot to send messages

(Image credit: Web Designer)

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

Create a custom Slack bot: Channel events

Set your bot up to listen for messages

(Image credit: Web Designer)

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

generate CSS

(Image credit: Getty/Future)

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

Create a custom Slack bot: Changing the message

Update the messages that the bot sends out

(Image credit: Web Designer)

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

Create a custom Slack bot: Ephemeral messages

Ephemeral messages are only seen by one user and can be deleted

(Image credit: Web Designer)

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

Create a custom Slack bot: The file upload function

Use the uploader and the FileSystem to upload images

(Image credit: Web Designer)

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

Create a custom Slack bot: Further resources

There's much more bot info to be found elsewhere

(Image credit: Web Designer)

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: