Early in the morning

Hello, stranger. I’m sensei Sun Tzu.

Oh yeah, yeah I’m Tony. Sure, I’m overweight panda, huh! You have a problem with that?! I smack your face shithead!
Easy Tony! Hold your temper! Excuse Tony’s manners. He’s been slightly impulsive since that Red Panda started bullying him for his weight. He’s a good panda otherwise. Me… and Tony… will be your mentors in this guide.

Yeah, listen to papa scum! ‘member who’s the panda here tho! Good, papa?
Very well Tony, here have a candy and go play with the dragons for a while. I will show our guests around.

I go papa. ‘hate stupid dragons… oversized lizards. I teach them manners… spitting fire shitheads…
At last. Tony is gone. However, we have to be quick. I will start (so to speak) without further ado! My courier told me you wish to build a Slack Bot. It is a matter of high importance to you I see but be patient, we will conquer it step by step. It will take us three sunrises to complete the teaching.
Firstly, we must prepare the battlefield. In today’s sunlight you shall understand the following principles of Sun Tzu’s “Art of Building Bots”:
- Learn what Slack Real-Time Messaging is;
- How to create a custom Slack bot integration and add it to your team’s channel;
- Learn the basics of node-slack-sdk;
- How to use node-slack-sdk to receive messages from Slack;
- How to use node-slack-sdk to send messages to Slack;
- How to use node-slack-sdk to update messages previously sent to Slack;
- How to build a Slack bot sending a formatted message with an image.
Look at these ancient, stone engravings placed along the pathway. They depict how to create a Slack bot in 8 simple steps. Follow them down to the lake and then come back. I shall put on some green leaf tea in the meantime.








Afternoon tea in Sun Tzu’s siheyuan


Sensei, our guest is back. Holly Budda! He brought ancestors’ rocks with ‘im! Puta madre, carcassonne, pierogi z grzybami!
Worry not my fellow panda, we shall put them back when the moon is high later this night.

Hey, yo’, don’t stare! Never saw a panda taking a piss?! Drink your tea mate, good for kidney IYKWIM.
A wise man takes no offense from a panda, so stay calm my friend and let’s carry on with our study.
I see you found all the rocks. Congratulations! If you followed them attentively you learnt how to create a custom Slack bot integration and your bot should now be available in Slack. Keep in your head that you must invite this bot to channels you want it to participate in. It’s a matter of security that bots don’t have access to all channels, so choose wisely.
As you have probably discovered your bot is silent. He never talks back to you. My dear friend, this is because he is out there waiting in the solitude of his dark, damp grotto. Should we want to speak with him we must learn to find a way to his heart.

Bitch please! I hate those lamb burgers. Bloody dragons always treat me with them… lamb… gives me horrible gases. Buuh… Oh yeah, sorry sensei, interrupted you? Will go grab eucalyptus smoothie. Cheers lads!
Another minute or two of peace. Let’s move on.
I shall now reveal you a secret behind custom Slack bots. The way we are going to build them and the reason it works is: they use something called Real-Time Messaging API. It’s based on WebSockets and we can use a phone conversation analogy to explain it.
Imagine this:
Alone in his grotto, your bot spends all day in lotus blossom position ready to observe your Slack channels with his third eye. You, as an aspiring Slack Ninja, want to communicate with him somehow. Luckily, he has a smartphone.
But hold! Not everyone can call your bot. In fact, his number is private. In order to call him, you must obtain a number called API Token (one of the stones shows where to find it). You should use this token to establish a long-lived connection with the bot.
Like with a regular phone call, this communication is bi-directional. The bot can listen and speak. The call lasts as long as you stay on a line. Remember: you call the bot, not the opposite. If you want the bot to call you, you must use Events API. This, however, requires setting up a server, so we will use Real Time Messaging for simplicity.
The are two paths to enlightenment. The Frisky Skylark path and The August Buzzard path.
The Frisky Skylark path
A real man is not intimidated by his lack of knowledge or experience, but seeks the truth and evolves despite his shortages and malicious comments!
Be brave, open gomix!
Should you find yourself wondering what gomix is, you must delve into these elder scrolls and come back when you feel ready.
Assuming you followed the Stones of Wisdom the only step to take is to insert your bot’s token into the .env file.

Gomix will build everything (wait a couple of seconds) automatically. That’s it. Test your bot in Slack now.

The August Buzzard path
You tricked me, colonel! A hood was covering your face all the time but now when the light touched it I can see I stand in front of a great, courageous statesman!
So, you wish to follow The August Buzzard path? Very brave indeed.
I must warn you, though. Before we get any further make sure to double check your inventory. Here, you may find this checklist helpful:
- Install node
- Sign up for GitHub account and install git
- Download and install Heroku Toolbelt
Now, draw your console colonel as we shall not turn back or withdraw.
Clone the bot code somewhere on your disk.
|
1 |
clone https://github.com/greatdaytocode-krzysztof/sun-tzu-the-wise-bot |
Enter newly created directory. Part one of this guide is located on a branch.
|
1 |
git checkout part-one |
Rename .env.schema file to .env and copy your bot token inside.
In your console run these commands:
|
1 2 |
npm install npm start |
Check if you bot replies. It should.
As dusk sets in, sensei asks you to enter his chamber to contemplate on The Code

After your own eyes experienced the presence of The Wisest Bot you may ask: “Why is this bot called Sun Tzu. Just like you. Is he your relative?”. And I would be forced to reply: “No! I am the wisest!”

Here you are paps! That smoothie place is a shithole. That asshole, Red Panda, has just messaged me a selfie on pandabook. And you know what it shows? Him, and a pack of hawksbill turtles working in that smoothie bar, mixing some bamboo into my smoothie!! You imagine? I’m bloody bamboo-allergic!
Calm down Tonso! You know we don’t want you to stress right? Remember what the doctor said?

Yeah, yeah, if I stress a lot I will extinct.
True, and we don’t want you to extinct right? You’re just too cute! Also, don’t forget you were diagnosed with a lung cancer. I told you to quit smoking those ginseng cigarettes!

You can’t tell me how to live! You’re not even my real father! I hate you!
And he slams the doors again…
You will come back to me when you get hungry you fat, penguin-bear!
Excuse me, my friend. Kids… they are so disappointing sometimes.
Back to our enlightened contemplation… where were we?
Oh yes, I was about to give you insight into details of building a bot.
Please take out your notepad (or IntelliJ, or Atom, or Sublime Text, whatever) and listen carefully.
- You must add @slack/client dependency. To do so type this in your console:
1npm install --save @slack/client - Observe package.json file. It should look alike:

- Add a start script to the same file:
123"scripts": {"start": "heroku local"} - Create a Procfile and add this line to it:
1web: node suntzu.js
heroku local will be invoked when you type npm start in your console. Heroku reads Procfile for instructions. It will run a process of web type by invoking node suntzu.js, as a side effect it will also read the .env file and load its contents into process.env (a node variable).
Are you following?
npm start => heroku local => node suntzu.js + .env
If you look at the gomix example you will notice a difference. Heroku local is substituted with a library called dotenv. Dotenv allows you to programmatically load variables from the .env file.
Now, take a look at the suntzu.js file.
We must first import all the dependencies into this file:
|
1 2 3 4 5 |
var fs = require('fs'); var RtmClient = require('@slack/client').RtmClient; var WebClient = require('@slack/client').WebClient; var RTM_EVENTS = require('@slack/client').RTM_EVENTS; var CLIENT_EVENTS = require('@slack/client').CLIENT_EVENTS; |
RtmClient is used to listen to messages from Slack and WebClient to post messages to Slack. Fs is just a standard node library for working with files. We will use it to read a quotes file. The quotes file is basically a storage keeping all quotes in one place.
Move on to the next line:
|
1 2 3 |
var bot_token = process.env.SLACK_BOT_TOKEN || ''; var rtm = new RtmClient(bot_token); var web = new WebClient(bot_token); |
We must load the bot token from the environmental variable.
Two objects are created: rtm and web. Both take bot_token as an argument. They use it internally to make authenticated calls.
Next, definitions of helper arrays:
|
1 2 3 |
const meditationImages ... const meditationPhrases |
These two are used by the bot to simulate ‘meditation’. After you ask him a question he first displays a short message and an image. After eight seconds that message is updated with a quote.
Next four lines of code are the essence of this program.
|
1 2 3 4 5 |
let stripsOfWisdom = readTheBookOfWisdom(); proveYouAreTheRealSunTzu(); wakeUpFromTheSpiritualMeditation(); becomeAllEarsToTheUnenlightened(); |
readTheBookOfWisdom
loads the quotes file, splits quotes by new line character and returns an array of quotes (stripsOfWisdom).
proveYouAreTheRealSunTzu
You could actually skip this method. It merely listens to CLIENT_EVENTS.RTM.AUTHENTICATED event. Which is broadcasted when the bot is successfully authenticated. It would not be triggered if your token is invalid for instance.
wakeUpFromTheSpiritualMeditation does only one ting. It start RTM connection.
becomeAllEarsToTheUnenlightened
Probably the most interesting one. Let’s take a closer look:
|
1 |
rtm.on(RTM_EVENTS.MESSAGE, (message) => { |
Subscribes to every message the bot is able to hear. This means that everytime someone writes a direct message or writes a message inside a channel the bot is invited to, your bot will receive it.
Be careful because it will also hear its own messages. Hence this line:
|
1 2 3 4 |
if ('bot_message' === message.subtype || 'message_changed' === message.subtype) { // Ignore the message if it comes from Sun Tzu itself return; } |
It is, a rather naive, method to filter out messages we don’t want to respond to. It’s enough for the purpose of this example. Later, we will study a library (a framework) called BotKit. It makes listening to message much simpler.
|
1 |
meditate(message, honorYourAudienceWithAWiseSentence); |
That last line invokes meditate function.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function meditate(message, callback) { let meditatingMessage = { "attachments": [ { "fallback": "Sun Tzu's inspired message should reveal itself here", "title": pick(meditationPhrases), "color": "#36a64f", "image_url": pick(meditationImages), "ts": 123456789 } ] }; web.chat.postMessage(message.channel, "", meditatingMessage, (err, data) => { setTimeout(() => callback(data), 8000); }); } |
We do three things in this method:
- Send a message with attachment web.chat.postMessage;
- Set 8 seconds timeout
- Invoke callback (honorYourAudienceWithAWiseSentence) on timeout.
You can finally see how to send an image to slack here. In this example, we are showing an image which has a publicly available URL, but you could upload a file to Slack instead.
|
1 |
"image_url": pick(meditationImages), |
Lastly, let’s look at honorYourAudienceWithAWiseSentence:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function honorYourAudienceWithAWiseSentence(data) { let inspiredReply = seekForAComfortingPieceOfWisdom(); web.chat.update(data.ts, data.channel, '', { attachments: [ { "fallback": "Sun Tzu's inspired message should reveal itself here", "text": inspiredReply, "color": "#36a64f", "footer": "Sun Tzu, The Art of War", } ] }); } |
Pick a random piece of wisdom. Ekhm… seek for the truth of course!
|
1 |
let inspiredReply = seekForAComfortingPieceOfWisdom(); |
Update the previous message (a meditation gif with a title) with a chosen quote. Note that we needed to know timestamp (data.ts) of the previously posted message and the channel we want to post to.
|
1 |
web.chat.update(data.ts, data.channel, '', { |
Alas! The moon is high and we must end here.
Come back tomorrow to learn how to rewrite this code with BotKit.

And bring me some food next time!
Selection of Tools & Resources used to prepare this article:
- I drew Panda and Sun Tzu. Well. I made an attempt 😛 It’s my first panda ever 🙂
- Rocks and stones images
- More stones
- Trust me, it’s the last stone resource
- Online tool for vectorizing images
- Tool for converting vectors into rasters
- Online tool for editing images
