Writing A Simple Slack Bot With Node slack-client

Last week, we held our first CareEvolution hackathon of 2015. The turn out was impressive and a wide variety of projects were undertaken, including 3D printed cups, Azure-based machine learning experiments, and Apple WatchKit prototypes. For my first hackathon project of the year, I decided to tinker with writing a bot for Slack. There are many ways to integrate custom functionality into Slack including an extensive API. I decided on writing a bot and working with the associated API because there was an existing NodeJS1 client wrapper, slack-client2. Using this client wrapper meant I could get straight to the functionality of my bot rather than getting intimate with the API and JSON payloads.

I ended up writing two bots. The first implemented the concept of @here that we had liked in HipChat and missed when we transitioned to Slack (they have @channel, but that includes offline people). The second implemented a way of querying our support server to get some basic details about our deployments without having to leave the current chat, something that I felt might be useful to our devops team. For this blog, I will concentrate on the simpler and less company-specific first bot, which I named here-bot.

The requirement for here-bot is simple:

When a message is sent to @here in a channel, notify only online members of the channel, excluding bots and the sender

In an ideal situation, this could be implemented like @channel and give users the ability to control how they get notified, but I could not identify an easy way to achieve that inside or outside of a bot (I raised a support request to get it added as a Slack feature). Instead, I felt there were two options:

  1. Tag users in a message back to the channel from here-bot
  2. Direct message the users from here-bot with links back to the channel

I decided on the first option as it was a little simpler.

To begin, I installed the client wrapper using npm:

The slack-client package provides a simple wrapper to the Slack API, making it easy to make a connection and get set up for handling messages. I used their sample code to guide me as I created the basic skeleton of here-bot.

This code defines a connection to Slack using the token that is assigned to our bot by the bot integration setup on Slack's website. It then sets up a handler for the open event, where the groups and channels to which the bot belongs are output to the console. In Slack, I could see the bot reported as being online while the code executed and offline once I stopped execution. As bots go, it was not particularly impressive, but it was amazing how easy it was to get the bot online. The slack-client package made it easy to create a connection and iterate the bot's channels and groups, including querying whether the groups were open or archived.

For the next step, I needed to determine when my bot was messaged. It turns out that when a bot is the member of a channel (including direct message), it gets notified on each message entered in that channel. In our client code, we can get these messages using the message event.

Using the slack-client's useful helper methods, I turned the message channel and user identifiers into channel and user objects. Then, if the message is a message (it turns out there are other types such as edits and deletions), I send the details of the message to the console.

With my bot now listening to messages, I wanted to determine if a message was written at the bot and should therefore alert the channel users. It turns out that when a message references a user, it actually embeds the user identifier in place of the displayed @here text. For example, a message that appears in the Slack message window as:

Is sent to the message event as something like3:

It turns out that this special code is how a link to a user or channel is embedded into a message. So, armed with this knowledge and knowing that I would want to mention users, I wrote a couple of helper methods: the first to generate a user mention embed code from a user identifier, the second to determine if a message was targeted at a specific user (i.e. that it began with a reference to that user).

Using these helpers and the useful slack.self property, I could then update the message handler to only log messages that were sent directly to here-bot.

The final stage of the bot was to determine who was present in the channel and craft a message back to that channel mentioning those online users. This turned out to be a little trickier than I had anticipated. The channel object in slack-client provides an array of user identifiers for its members; channel.members. This array contains all users present in that channel, whether online or offline, bot or human. To determine details about each user, I would need the user object. However, the details for each Slack user are provided by the slack.users property. I needed to join the channel member identifiers with the Slack user details to get a collection of users for the channel. Through a little investigative debugging4, I learned that slack.users was not an array of user objects, but instead an object where each property name is a user identifier. At this point, I wrote a method to get the online human users for a channel.

Finally, I crafted a message and wrote that message to the channel. In this update of my message event handler, I have trimmed the bot's mention from the start of the message before creating an array of user mentions, excluding the user that sent the message. The last step calls channel.send to output a message in the channel that mentions all the online users for that channel and repeats the original message text.

Conclusion

Example

My @here bot is shown below in its entirety for those that are interested. It was incredibly easy to write thanks to the slack-client package, which left me with hackathon time to spare for a more complex bot. I will definitely be using slack-client again.

 


  1. or ioJS, if you would prefer 

  2. I find hackathons to be a bit like making a giant pile of sticks in the middle of a desert; it's an opportunity to get creative and build something where there seems to be nothing…using sticks…or in my case, a Node package and Slack 

  3. I totally made up the user identifier for this example 

  4. I used WebStorm 9 from JetBrains to debug my Node code, a surprisingly easy and pleasant experience 

Ten Years

This month marks ten years since I first set foot in the US. As I waited in line at immigration, tired from the flight and daunted by everything that might happen next, it was easy to forget everything that came before. Just three weeks earlier, my workplace had been tense with news that another wave of redundancies was sweeping through and I was unsure in what direction I was heading1. I was ready for a change, but did not want the uncertainty of finding a new job or the certainty of choosing to leave the one I had. I was living a step ahead of my means with little attention paid to the future. I was smoking. I was making dubious decisions or avoiding decisions entirely. I was feeling disenfranchised, misplaced, and numb.

One afternoon our manager called us all into a meeting room. There, he informed us of two positions available in the US and asked if any of us were interested. It felt like the silence lasted a long time although it was probably only a few seconds. No one was volunteering. I do not know the trigger  — my desire for a change, the allure of working in the US, or my need to get control of my life, but slowly, I raised my hand. I remember rationalizing it as no big deal, after all, I was only expressing interest, it was not like I would be whisked away to a plane immediately. With the raise of my hand, so began a series of small, easy decisions that led to the biggest self-directed change of my life so far.

Within the three weeks from when I raised my hand to when I stepped off the plane in Detroit, I packed, paused, and displaced my life. Boxes were filled, paperwork was filed, and farewells were planned. There was no time to stop and think about what I was doing, just lots of small decisions to make — accept or negotiate the contract, pack or throw away my things, take or leave my guitar, stay or go, sink or swim. All along the way, I kept telling myself it was not forever, it was no big deal. I was only going for a couple of months to meet the customer face-to-face; work (and a longer stay) was always going to be dependent on the acquisition of appropriate work visas. It was no big deal.

And so it was. Three weeks flew by. My sister, my parents, my new boss in the US, my old colleagues in the UK, my friends (including my housemate, who was seriously ill at the time), and many more all helped in some way. I am incredibly grateful to their support, it was amazing. During the whole experience, trepidation wrestled with excitement. Seconds after the taxi pulled away, leaving my parents and friends as they waved goodbye, excitement turned to panic.

What the hell am I doing?

I repeated that phrase in my head many times between London and Detroit. When the taxi left my old home. When the taxi left me at the airport. When I was pulled from the security line for "special screening". When I sat on the plane. When the plane took off. At least once per hour during the flight. When I landed. When I got into the immigration line. Over and over.

What the hell am I doing?

I am pretty sure I was terrified, but just like the small decisions that got me there, I focused on the immediate situation and did my best to ignore everything else. I think excitement and terror are pretty much the same thing but with different interpretations. As I accepted the situation as an adventure, the terror would subside and excitement returned.

Blimey, I'm actually going to America!

That was how my first few weeks in the US continued. A mixture of terror and excitement, depending on the situation and how I let myself accept it. It was the beginning of something new and ten years on, I cannot imagine doing any differently if it were to happen all over again. It was by far the best decision I ever made because I learned the value of making a decision instead of letting fate decide. I faced my own anxieties head on and made a decision to challenge my fear. The amazing sense of achievement that came from deciding for myself was life-affirming. While it took me another nine years to take that moment of control over my anxiety and begin learning how to harness it on a day-to-day basis, I still look back on that decision and the many ways it has changed my life. In a moment, I went from feeling disenfranchised, misplaced, and numb, to engaged, excited, and driven.

Of course, that first day in the US was merely the beginning, a lot has happened since and a lot more will happen yet. Though my move was certainly no panacea to my problems — there were many difficult challenges to over come, it was a catalyst for solutions, an opportunity to grow, and a clear example that fear alone could not stand in my way if I could find the courage to face it. It is a lesson that I have applied many times since; from winning the CodeMash Pecha Kucha contest, to marrying my amazing wife, so many achievements began in an otherwise unremarkable moment where I pushed my fear aside and made a decision to try.

So, whatever the next ten years hold for me, it is not fear, but small moments like raising my hand in that meeting room that will shape them. Can you say the same? Where will your decisions take you?


  1. Unlike in places such as Michigan, where employment is considered "at will" and can be terminated at any time, if a company in the UK wants to downsize, they must go through a process of making positions redundant. That means the position will no longer exist and as such, the organisation cannot hire someone else to perform that job. For a better explanation and more information, check out https://www.gov.uk/redundant-your-rights/overview 

Sometimes Size Matters

Some may find parts of this story NSFW or offensive, if you think that might be you, I suggest you read something else (you have been warned).

This week I am writing about something a little different from my usual posts and revisiting my past. While the general content of what you are about to read is based in truth, some events have been merged into one for the sake of brevity or because my memory is not accurate. Nothing has been deliberately fabricated, a lot has been paraphrased, and I have changed the names to protect me from embarrassment (though I don't think it will work).


It had been over a year since we had last met when I got a call from Susan inviting me out for a date. If I recall correctly, it was somewhere around Valentine's Day, but I might just be making that up. I was living near Cambridge at the time and Susan was in Manchester, but I was single and Susan was gorgeous, so I gladly accepted.

I had first met Susan sometime during my third year at university in Manchester. My friends and I had ventured out to a night club called 5th Avenue for indie night. I remember my visits to 5th Avenue fondly, throngs of tightly packed students dancing with our hands behind our backs like a legion of Ian Browns and Liam Gallaghers, mouthing the words to "Fools Gold" by The Stone Roses, "Cigarettes and Alcohol" by Oasis, and many more. The following playlist will give you just a taste of the music we swayed and swaggered to at 5th Avenue back in the mid-90's. Consider listening to it while you read on.

On the night I met Susan, I was wearing my favourite trousers, unaware that they were perhaps a little too tight for me, and a shirt, untucked as was the style for the very best indie fans. After waiting in line outside for the privilege of paying to get inside, I grabbed a drink at the bar. Back then, I rarely danced without a little lubrication to dull the sting of staring strangers — insecurity had grown deep roots. Before long, I was swaying amid the mass of other club goers, enjoying the 12-inch remix of "I Am The Resurrection". I spotted Susan almost immediately, her long blonde hair and bright blue eyes were hard to miss. My heart skipped and panic set in when we briefly made eye contact.

Did she look at me? Was that a smile? Oh, fuck. What if she likes me? What do I do now?

I have always been prone to over-thinking things. When others would find joy in a moment, I would work hard to find the pain. I was also terribly shy and, at that time, had yet to lose my virginity, so any connection with a woman was always panic-inducing to me. Where would it lead? What do I do? How do I act? It was terrifying. So, I did what made the most sense to me and avoided eye contact at all costs. So much so, that eventually, Susan had enough and came over to me.

"Why are you ignoring me?" she asked, "Clearly, so rude."

Susan said "clearly" a lot. It didn't bother me.

"I say 'clearly' a lot," she said, "Clearly."

We both laughed.

Susan and I really hit it off and I had an amazing time dancing, talking, and smoking with her, and I had the best time making out with her in a dark corner. The close proximity of an attractive woman, the kissing, the touching, the sparkling blue eyes, the perfume; for a 20-something virgin it was arousing to say the least. I adjusted myself to try and hide the erection that was lying against my thigh — in my tight trousers, it was way more visible than I felt comfortable with. When we stood up to dance again, Susan brushed against penis with her hand, and her eyes went wide. She was grinning. When she dragged me to her friend on the dance floor to eagerly show off the growth lying against my leg, I blushed and nervously glanced around.

"I can't help it," I implored her, pushing her hand aside as she continued pointing at my readiness.

She just smiled even wider. "I know!" she said, excitedly.

When we reached the end of the night, we stood together outside the club. Susan was leaning against the wall of the club. She pulled me in for another kiss as we waited for Susan's taxi home.

"Why don't you come back to mine?" she whispered in my ear, her fingers tickling the end of my still aroused manhood (it is very difficult to get rid of an erection when someone keeps playing with it). My heart was racing. Here I was with a really beautiful girl and the possibility of a life-changing event, and I was terrified. I was terrified since I'd never done it before, because I didn't want to get anything wrong and because I was over-thinking everything.

So, I declined. She pleaded, pouted, and generally enticed me, but I dug deeper into my insecurities and declined again, and again. In my head, I justified it to myself.

I really like this girl. She's gorgeous. It would be wrong to take advantage. That's what this would be, right? I'm a gentleman, I should decline.

Susan climbed into the taxi, leaned out of the window to check I was sure about not joining her, and then blew me a kiss goodbye.

In romantic, Hollywood-style stories, that would be the last time I saw her until some serendipitous event brought us back together in a romantic liaison, except maybe with less inappropriate tight-trouser torpedo. But this isn't that kind of story. Although there was a lot more to our original meetings than what is told here, let's skip that and get back to that phone call a year or two later around Valentine's Day.

After chatting on the phone with Susan, I got on the Internet and booked a hotel room in Manchester. This was going to be a night out drinking, so a hotel room made sense. Not to mention that something might happen between Susan and I.

The train ride to Manchester was tense for me as my mind raced about my upcoming date with Susan. I have always found it difficult to determine the difference between excitement and terror. In fact, I'm pretty sure they are the same thing with different interpretations, so I often flip between both states several times a minute. I walked from Manchester Piccadilly train station, past the night club where we first met, and on to the bar where we had agreed to meet for our date.

I entered the bar and nervously glanced around for Susan. She wasn't there, so I bought two drinks, downed one, and found a seat. I made sure to sit somewhere I could see the door, but not immediately visible from it, like I was comfortable on my own and totally not waiting to meet anyone (after dropping everything and travelling a couple of hundred miles from Cambridge to see her, I didn't want to seem too eager). When Susan walked in, my heart skipped; I had forgotten those eyes. She saw me almost immediately and came straight over to where I was sat. We hugged and got to chatting.

It was clear from the beginning of the date that Susan had an agenda. I was not an experienced dater at the time and definitely had my share of trust issues. When we got to the second bar of our date, there was a shortage of seats so Susan eagerly suggested she should sit on my lap and I agreed. Even though I was completely into her, my insecurity still flexed its muscles when she reached under my shirt and caressed my pudgy belly, and I flinched as if she might rip my heart out or otherwise humiliate me. Yet Susan persevered and I was amazed that she genuinely liked me. By the end of the night, we were lying next to each other in my hotel room, mostly naked, watching amputees and amputee fetishists on Maury Povich, Jerry Springer, or some other crass TV show. It was hard not to find it hilarious, until Susan's hand wandered into my underpants.

"Am I doing it right?" she asked after about three minutes, a look of confusion on her face.

I reassured her that she was, wondering why she asked such a strange question and with so much concern. I started to reciprocate and for a while, things appeared to be going well. I was very excited, or terrified. Then, abruptly, she stopped.

"Is everything OK?" I asked.

"Yes, let's just not, OK?" she said, instantly transferring her earlier confusion over to me.

"OK," I said.

We naked hugged, finished watching and laughing at the show, then went to sleep. The next time I saw Susan, it was to watch a movie together as friends. We never spoke about that night in the hotel room, and it took me way too much time to piece everything together, but eventually, I realised the cause of Susan's confusion.

Susan had never forgotten that first time we met when my tight trousers left little (although, it turns out, something) to the imagination, except she had assumed the bulge in my trousers was caused by a flaccid penis awaiting arousal. So, when she eagerly reached into my underpants in that hotel room bed years later, she had been expecting me to deliver a bigger package than she found. All the time I had been terrified about her thinking less of me for having an erection in public, that her and her friend had laughed at me just because I found her sexually attractive, when in fact, they thought I was packing something extra special in my pants. There are some things that stick with you forever.

Though Susan and I are still friends, we have never spoken about any of this; it could be that I still have it all wrong. However, it has always brought a smile to my face to recall the day I learned for myself that size can matter. Thankfully, my imaginary giant penis brought a friend into my life, and who can be upset about that?

Ignorance Is No Joke

The age of social media has given us a new host for the pathogens commonly referred to as memes. Quite often these are innocuous, humorous quips that could lighten anyone's day. Unfortunately, they are also often misleading, false, and destructive. Many lie, and some, like the one that brought me to write this post, celebrate ignorance in a way that saddens me.

The specific meme that I am referencing implies that learning algebra was a waste of time. It is often posted with some comment that seems to indicate algebra is nothing more than a pointless punishment thrust upon poor school children for no other reason other than to satisfy the blood lust of an evil teacher.

Algebra meme
Algebra meme

Why does this upset me?

It upsets me because it not only validates ignorance, but it encourages it, it legitimizes the desire for people to not try learning it (imagine a kid at school seeing this, what is it teaching them about perseverance and education?).  I get it; if you struggled with algebra, it's comforting to laugh it off as useless anyway, but this is short-sighted. Even in jest, sharing this sentiment is damaging. This comfort blanket for you might mean an under-achieved potential for your child, or someone else's. Many future and current careers use and build upon the foundations laid by algebra in school (you think Facebook was created by people who did not get algebra?). Let's not do a disservice to the next generation by perpetuating and legitimizing ignorance just so we can feel more comfortable with our own. We all use the principles of algebra, whether we recognise it or not. Reworking a recipe for four people so that it will feed two, splitting a bill in a restaurant, or working out a budget, we use algebra. It may not look like algebra because we rarely write down some equation and then tell us to "solve for X", but it is algebra all the same.

Of course, it is not just anti-algebra sentiment that is being spread by misleading or seemingly innocuous memes. There are far more egregious examples, like those that perpetuate the myths that vaccinations cause Autism or harm more people than they help. These memes have the potentially deadly effect of reducing herd immunity and putting our most vulnerable individuals at risk; those that cannot be vaccinated at all.

Memes are powerful and yet so easy to spread. Just as with viruses that affect our physiology, some memes can be incredibly damaging, like a virus to the larger organism of society. My challenge to everyone is to think before sharing a meme, to fact check, to try to ensure a damaging myth or lie is not being perpetuated, perhaps share more positive memes that encourage rather than discourage. I know that some will think I am being far too serious and want to assert that "it's just a joke", but that does not change the way I feel, nor does it change the impact that "joke" has on others. I am not asking anyone to lose their sense of humour, just to think twice and share memes responsibly. Think about who might see a meme you share, what it might mean to them, and what they might learn from it.

Thanks for reading. Learn algebra.

Breaking The Code

Last week saw the return of Learn Something, a monthly software hacking event held at the offices of Fanzoo Technology in Ann Arbor. Each month, attendees can choose to hack on their own projects or take part in the monthly Learn Something challenge. Teams and pairing are encouraged, providing opportunities to work with new people, tools and techniques.

The message we had to decode
The message we had to decode

This month the challenge was to decipher a message encoded using a simple substitution cipher. Participants started with a copy of the encoded message (known as the ciphertext) and a text file of words from the English language. The message (shown above) is one example of a cipher, known as 'alienese', that appeared in several episodes of Futurama. Though fans of the show have already solved the cipher and we could have searched the Internet for the solution, our goal at Learn Something was to create a software solution that could solve it (or at least narrow down the possibilities1).

What is a simple substitution cipher? A simple substitution cipher is a way of encoding a message by replacing (substituting) each letter of the alphabet with a different letter or symbol. For example, if we had the substitutions t to a, h to g, and e to o, the word the would become ago.

Alex Zolynsky, one of the organizers of Learn Something, provided a clearer print out of the message with punctuation symbols written in red (see below). The intention was to ignore the punctuation and focus on the actual words, so before we started, one of the participants assigned a letter to each symbol such that we had the message written in a more familiar alphabet (and one that was easier to type on our laptops).

The annotated message
The annotated message

The resulting message read:

abbc bdefg hgij kble cmna ompf mlc pangaebc jpkgai nb qgo emq cmllgf

Clearly, our work was far from done, but before I continue with this blog, I need to come clean.

At Learn Something last week, I was buried in some work of my own, and other than the occasional interjection, I was not involved in the challenge. However, at the end of the night, I took the cipher home with me and the following evening, I got stuck in. It took me about three hours to crack the message. How did I do it?

Well, there are many ways to tackle breaking a substitution cipher. For example, if the ciphertext is long, we could use frequency analysis to gain some hints at the substitutions2. However, this ciphertext was short and as such, frequency analysis was not particularly helpful3. Instead, I decided to follow the same track as the participants at Learn Something and take the longest word in the phrase then find words in English that had the same pattern and use that to start reducing the possibilities. So, I opened up LINQPad (my tool of choice for hacking around), and got started.

The longest word in the ciphertext is pangaebc. To match words that follow the same pattern, I treated the word as its own ciphertext and made another substitution, just as the symbols had been substituted for letters earler. This allowed me to normalize different words to see if they matched the same patter. Only words that normalized to the same string would be of the same pattern. So, for pangaebc, the normalized version is abcdbefg. Examples of English words that match this pattern are airfield, windiest, and the grosspustular, each of these has the pattern abcdbefg when normalized. In fact, in the dictionary I used there were over 500 matches, over 500 possibilities for the word that could be in our decoded message. Each of the possible matches for the longest word provided a possible part of the substitution cipher. The next step was to take another word from the ciphertext and find English words that matched the pattern of that ciphertext word as well as the substitutions found from the first word, pangaebc. This started to build up a tree of candidate words for the decoded message. Each node in the tree contained words that match the substitutions required by its parent node but also matched the pattern of a word in the ciphertext. By recursing this approach for each word in the ciphertext sentence, a tree of possible plaintext4 sentences could be generated.

Once all words in the ciphertext had been processed, I could take the branches of the resulting tree that included the same number of nodes as words in the ciphertext and determine the possible substitutions that might give the right plaintext solution. This produced 15 possible sentences. It was then up to me to read each one and pick the one that made the most sense. Of course, I'm not going to spoil it by telling you the solutions I found. Instead, I encourage you to give the challenge a go for yourselves and see what you come up with (we both know you could cheat by searching the Internet for an answer, but you're better than that, right?).

I really enjoyed tackling this problem. Not only was it a fun distraction, but now I have a LINQPad query that can solve any substitution cipher as long as I know what language in which the message is written. I am definitely looking forward to the next time I attend Learn Something. Hopefully, your interest is piqued and I will see you there. In the meantime, if you give this challenge a go, I would love to hear how you tackled it, what you did differently to me, and what you learned. Until next time, thanks for reading.

Featured image: "Confederate cipher disk" by RadioFan (talk) – I (RadioFan (talk)) created this work entirely by myself. Licensed under CC BY-SA 3.0 via Wikipedia.


  1. to fully solve it programmatically would have needed our software solutions to have understanding of English sentence structure, which was thankfully outside the scope of the challenge 

  2. assuming we know the language in which it is written 

  3. I did try it just to see, but frequency analysis needs a longer ciphertext than we had for this challenge 

  4. decoded text