This article was peer reviewed by Dan Prince and Matthew Wilkin. Thanks to all of SitePoint's peer reviewers for making SitePoint content the best it can be!
Slack has a certain appeal and huge fan following in both developer and non-developer tech communities. Its slick user-interface, concept of teams and channels to keep communication separate and relevant, tons of integrations for productivity (Dropbox, Box, Google Calendar, Hangouts etc) and things like giphy and reminders, make it fun to use. Also, their APIs help developers extend the functionality and build a custom experience for their team.
This tutorial aims to help you get up and running with a simple node app that turns your Slack channel into a custom command-line terminal. We'll use a helper module called slack-terminalize (disclaimer: I developed it), that abstracts away the initial processing of messages. It uses Slack's Real-Time API Node client and prepares a bot to listen and respond to your requests.
Note that we won't be using Slash Commands here, but instead we'll interpret regular messages as commands. So if you were looking to learn about Slash Commands, this might not be the appropriate tutorial.
While there are many fancy hubot scripts that respond to natural language queries, a lot can be achieved with short commands and minimal keystrokes, as any Linux fan would agree. Simple commands especially make sense in a mobile device, helping you type less, do more. If you think about a command-line system, most of the times what the shell is doing is the grunt work of fetching, parsing, tokenizing and dispatching the commands (a gross oversimplification, I know).
With that in mind, I felt the need for a module which could do exactly that. A shell for Slack channels, if you will. With a process-and-dispatch approach and a plugin-like architecture to add custom commands, slack-terminalize abstracts things so you can focus more on defining the behavior of the app instead.
First, let's create a new bot user for your team, that can take your orders! Go to https://<your-team-name>.slack.com/services/new/bot, choose a username for it, and hit Add Bot Integration.
Copy the API token shown to you, as this is required for your bot to be able to interact with the channels. Configure the other details of the bot, its profile image and real name, and hit Save Integration.
From the list of dependencies in package.json, the only required dependency is slack-terminalize, but since the sample app has an example to show how to handle asynchronous commands, the request module is used to make REST calls.
All the JSON files you might need for your app can go here. And I say 'can' because it's fairly flexible and you can change it to work with a different directory via the configuration parameters (more on that later). This is just one of the many ways you can structure your app, but if you're new to Slack integrations, I suggest you stick with this.
This is what makes adding custom commands a piece of cake. Each command is represented by a key-value pair: with the key being the command name (I'll call it the primary name), and the value being an object with custom key-value pairs that you would want to use for the command.
alias – these are the aliases (let's call them secondary names) for the command, which can be used in the slack channel to invoke the command as well. It's best to keep the smallest name as the primary name and more meaningful, longer names as aliases.
exclude – a flag to indicate if this command should be shown in the list of commands available to the user. Some commands might just be for development purposes and/or helpers that need not be exposed to the user (e.g. the error command above).
Of the above, alias is the only key that is looked up to map user-typed commands to its primary name. The rest of them are optional and you're free to use any fields inside the command object as you see fit.
This is where the magic happens, the place where you define the command behavior. Each command specified in config/commands.json should have its matching implementation here, with the filename matching the key (primary name) used in that JSON. That is how the dispatcher invokes the correct handler. Yes, a bit opinionated I agree, but useful and customizable nonetheless.
"description": "To get help on all supported commands, or a specified command"
"description": "Fetches details of the specified Ruby gem"
Notice again, that the key names in this file are same as the file names in commands/ directory.
Replace the value for SLACK_TOKEN in index.js with the one for your bot. CONFIG_DIR and COMMAND_DIR are to tell slack-terminalize where to look for config and command implementations respectively.
Log in to your Slack team, either on web or app. The bot is added to #general channel by default, but you can invite the bot to any of the channels, even private ones, with the Slash Command: /invite @<your-bot-name>. As soon as you type /invite @, Slack should automatically suggest you the usernames. If you don't see your bot listed there, go back and check that you have integrated the bot correctly.
Type help or halp (the alias, remember?) in the channel and 'voila!', the bot should respond to your request. Go ahead and play around with commands/help.js to change what you see in the response. As you can see from the implementation, this command just loads the command details from config/commands.json file to respond, so it's synchronous. Sometimes, you might need to do asynchronous tasks, like querying a database or calling a REST endpoint, to fetch the response. Let's see how to go about that.
As I mentioned before, I use request module to make REST calls and the following code snippet (the gem command) searches for the gem name that user types in Slack. Take a peek at commands/gem.js and you'll see that it remembers the channel that the message was posted in (using a closure) and posts back the response into the same channel!
Again, try playing around with the formatting of the response in commands/gem.js to get the hang of it. Now we have a bot listening on invited channels and responding to our requests. Let's see how we can add custom commands.
Add your new command in config/commands.json. As mentioned before, the key will be the primary command name. Aliases for the command go in as an array of values in alias, as shown below.
"description": "Brief description of what the command does"
Currently, command names with space in them are not supported. Create a file with the same name as the primary name of your command above (in this case, your-command-name.js) in commands/ directory. Assign module.exports to the command implementation function, as shown below.
// implement your logic here..
Refer to the documentation of the node-slack-client, for more on the User and Channel objects.
Program your new command, restart the app and that's it! You should have your new command working. Type in the command and see if you get the expected response back.
The slack-terminalize module takes two parameters, an options object and a config object.
For more on the parameters, you can check the documentation here.
Go define some cool commands for your team: have fun and increase productivity.
Fork the project slack-terminalize and its sample app. Play around, contribute and help improve it. If you spot any bugs, create an issue on the repo!