Slack Bot

Use Case

Slack is an indespensible tool for many organizations. People have found many uses beyond simple chat communication. One prime example is ChatOps, which enables teams of developers to collaborative communicate and perform ops actions at the same time in a shared tool.

In this guide we’ll demonstrate how to build a SlackBot which receives webhook events from Slack, stores data in AWS DynamoDB, and publishes a daily report in a Slack channel.

Many people use Slack reactions to provide emotional reactions to a message:

Slack Reactions

The Slack bot we will build will count the reactions each person received in a day. Here, chase received one :pray: (thanks) reaction, one :ice_cream: reaction, and one :yum: reaction. Each day at 5PM the bot will send a message to the #general channel showing how many reactions each person received.

To make this all work, we will be combining the functionality of four node types:

  • RestApi to receive reaction events from Slack
  • Function to handle reaction events and the daily 5PM report generation event
  • Timer to emit the daily 5PM report generation event
  • Table to keep a running count of the number of reactions for each user

Prerequisites

  • Stackery account (Sign-up here)
  • New or existing Slack Workspace you administer
  • An understanding of Git to be able to copy the contents of the example repo to your own repo

How To

There is a pre-built stack at https://github.com/stackery/slack-reactions. This guide will use that as a basis.

  1. Create the Slack App
    1. Create a new Slack App. Set App Name to Reactions Bot (or another name of your choice). For Development Slack Workspace choose the Slack Workspace you want to count reactions for.
    2. On the Basic Information screen, note the Verification Token in the App Credentials section. We’ll use this in a minute.
    3. Select OAuth & Permissions under Features in the navigation sidebar on the left. In the Scopes section, select the following Permission Scopes:
      • Chat -> Send messages as Reactions Bot
      • Reactions -> Access the workspace’s emoji reaction history
    4. Click the Save Changes button at the bottom of the Scopes section.
    5. Click the Install App To Workspace button in the OAuth Tokens & Redirect URLs section at the top of the page. Go through the OAuth approval process to authorize the bot with your Workspace.
    6. Back in the OAuth & Permissions page of Slack, find and note the OAuth Access Token value in the OAuth Tokens & Redirect URLs section at the top.
    7. Keep this window open, you’ll need it one more time after we deploy the Stackery Stack below.
  2. Create the Stackery Stack, which will look like the following when complete: Reactions Bot Stack

    There are many steps to create the stack, feel free to save periodically.

    1. Create a new Stackery stack. Select a Git provider (AWS is easiest if you just want to get going quickly). In the next screen name the stack “slack-reactions” (or another name of your choice).
    2. Delete all the nodes in the new canvas by selecting them and pressing the Delete key on your keyboard.
    3. Add a new Rest Api node.
      1. Set the Method to POST
      2. Set the Endpoint to /events.
      3. Click the Submit button at the bottom of the editor pane to save the new values.
    4. Add a new Function node.
      1. Set the Name to Ingest.
      2. Paste the following code into the Source editor, overwriting the current code:
         const stackery = require('stackery')
        
         module.exports = function handler(message) {
           const body = JSON.parse(message.body.toString());
        
           if (body.token != process.env.VERIFICATION_TOKEN) {
             throw new Error('Invalid Verification Token received from Slack!');
           }
        
           switch (body.type) {
             case 'url_verification':
               // Respond to the initial verification challenge when first setting
               // up integration with Slack
               return body.challenge;
        
             case 'event_callback':
               // Here, we use Stackery's waitFor parameter to specify that we
               // want to return as soon as the message is transmitted, rather
               // than waiting for the promise to resolve, because we need to
               // make sure that we respond to Slack within three seconds
               return stackery.output(body.event).then(() => ({ statusCode: 204 }));
        
             default:
               throw new Error('Unrecognized event type: ', body);
           }
         }
        
      3. Add an environment variable named VERIFICATION_TOKEN with the value ${config.slack.verificationToken}. This will set the VERIFICATION_TOKEN environment variable to value set in the environment configuration store
      4. Click the Submit button at the bottom of the editor pane to save the new values.
      5. Connect the POST /events Rest Api node output to the input of this Ingest Function node.
    5. Add a new Function node.
      1. Set the Name to Handle Event.
      2. Paste the following code into the Source editor, overwriting the current code:
         const stackery = require('stackery');
         const crypto = require('crypto');
        
         module.exports = function handler(event) {
           switch (event.type) {
             case 'reaction_added':
               // Tell the Table node to increment the user's attribute for this
               // reaction emoji
               return stackery.output({
                 action: 'update',
                 where: {
                   id: event.item_user
                 },
                 increment: {
                   [`:${event.reaction}:`]: 1
                 }
               });
                          
             case 'reaction_removed':
               // Tell the Table node to decrement the user's attribute for this
               // reaction emoji
               return stackery.output({
                 action: 'update',
                 where: {
                   id: event.item_user
                 },
                 increment: {
                   [`:${event.reaction}:`]: -1
                 }
               });
                          
             default:
               throw new Error('Unknown event type: ' + event.type);
           }
         }
        
      3. Click the Submit button at the bottom of the editor pane to save the new values.
      4. Connect the Ingest Function node output to the input of this Handle Event Function node.
    6. Add a new Table node.
      1. Set the Name to User Reactions
      2. Set the Hash Key to id
      3. Click the Submit button at the bottom of the editor pane to save the new values.
      4. Connect the Handle Event Function node output to the input of this User Reactions Table node.
    7. Add a new Timer node.
      1. Set the Name to At 5PM Each Day
      2. Set the Timer Type to Cron
      3. Set the CRON string to 0 0 * * ? *. This will trigger the Timer to run at UTC midnight (5pm PDT). If you’d like it to run at another time change the second number to the hour you prefer.
      4. Click the Submit button at the bottom of the editor pane to save the new values.
    8. Add a new Function node.
      1. Set the Name to Generate Report
      2. Set the Outputs value to 2 so we can send messages to interact with the User Reactions table and also send a message to a Function to forward the message on to Slack.
      3. Paste the following code into the Source editor, overwriting the current code:
         const stackery = require('stackery');
        
         module.exports = function handler(message) {
           // Get reaction records from connected Table
           return stackery.output({ action: 'select' })
             .then(result => {
               let report = 'Reactions Today:';
        
               // We get the 0th item from the output response because
               // the Table node is connected to this function via the
               // first (of only one) wires
               const users = result[0].records;
                          
               // Generate Slack reaction report message
               for (const user of users) {
                 report += `\n<@${user.id}>: `;
        
                 // Grab all attributes that begin with `:`, these are
                 // reactions
                 const reactions = Object.keys(user)
                   .filter(name => name.startsWith(':'));
        
                 // Append the emoji as many times as user had the reaction
                 for (const reaction of reactions) {
                   report += reaction.repeat(Math.max(0, user[reaction]));
                 }
               }
                          
               // Output report message, then delete all current records
               return stackery.output({ text: report }, { port: 1 })
                 .then(() => {
                   const deletePromises = users
                     .map(user => stackery.output({
                       action: 'delete',
                       where: {
                         id: user.id
                       }
                     }))
                              
                   return Promise.all(deletePromises);
                 })
             })
         }
        
      4. Click the Submit button at the bottom of the editor pane to save the new values.
      5. Connect the At 5PM Each Day Timer node output to the input of this Generate Report Function node.
      6. Connect this Generate Report Function node’s first (top) output to the input of the User Reactions Table node.
    9. Add a new Function node.
      1. Set the Name to Message Slack Channel
      2. Paste the following code into the Source editor, overwriting the current code:
         const stackery = require('stackery');
         const https = require('https');
         const querystring = require('querystring');
        
         module.exports = function handler({ text }) {
           // Encode the Slack message data with Form URL Encoding
           const postData = querystring.stringify({
             token: process.env.OAUTH_TOKEN,
             channel: 'general',
             text 
           });
        
           const options = {
             hostname: 'slack.com',
             path: '/api/chat.postMessage',
             method: 'POST',
             headers: {
               'Content-Type': 'application/x-www-form-urlencoded',
               'Content-Length': Buffer.byteLength(postData)
             }
           };
        
           // Send the message request to Slack. There are simpler
           // ways to do this using 3rd-party Node.js modules, but
           // for this demo we go old school and use only built-in
           // HTTPS request support.
           return new Promise((resolve, reject) => {
             const request = https.request(options, response => {
               if (response.statusCode < 200 || response.statusCode > 299) {
                 const err = new Error(`Request failed (${response.statusCode})`);
                 reject(err);
               } else {
                 // Siphon off response data, but don't do anything
                 // with it
                 response.on('data', chunk => undefined);
        
                 // Return once the response has been sent
                 // successfully
                 response.on('end', () => resolve());
               }
             });
                        
             request.on('error', err => reject(err));
        
             // HTTP connection created, send request data
             request.write(postData);
             request.end();
           }); 
         }
        
      3. Add an environment variable named OAUTH_TOKEN with the value ${config.slack.oauthToken}. This will set the OAUTH_TOKEN environment variable to value set in the environment configuration store
      4. Click the Submit button at the bottom of the editor pane to save the new values.
      5. Connect the second (bottom) output of the Generate Report Function node to the input of this Message Slack Channel Function node.
    10. Click the Save button to save the stack.
  3. Update Environment Config
    1. Navigate to the Stackery Environments manager by clicking on the Environments link in the navigation bar at the top of the dashboard.
    2. Select the development environment if it isn’t already selected.
    3. Add the following configuration properties using the values noted above when creating the Slack App:
       {
         "slack": {
           "verificationToken": "<verification token>",
           "oauthToken": "<oauth token>"
         }
       }
      
    4. Click the Save button below the configuration editor.
  4. Deploy the Stack
    1. Navigate back to the stack by clicking on the Stacks link in the navigation bar at the top of the dashboard, then selecting the slack-reactions (or whatever name you used when creating the stack).
    2. Click the Prepare Deployment button just below the Save button.
    3. Wait for the deployment to be prepared. This usually takes about one minute.
    4. Click the Deploy button. This will open the AWS CloudFormation. Click the “Execute” button in the top right to deploy the new stack.
    5. Back in the Stackery dashboard, wait for the deployment to be deployed. The deployment in the left sidebar will move from the Prepared Deployment section to the Current Deployment section once it has been fully deployed. The initial deployment takes around 10-15 minutes. Subsequent deployments take about a minute.
    6. Note the domain of the API. In the Stackery dashboard, select the current deployment if it is not already selected. In the canvas area on the right, select the POST /events Rest Api node. In the properties panel on the right find and copy the Domain Name.
  5. Connect the Slack App
    1. Go back to the Slack API page for your app (select your app here if you need to reopen it).
    2. Navigate to Event Subscriptions under Features in the sidebar navigation.
    3. Switch Enable Events to the On position
    4. Enter the following into the Request URL field: https://<your Stackery api domain from above>/events. After a few moments the page should successfully verify the URL by making a request to your new Rest Api.
    5. Under Subscribe to Workspace Events click the Add Workspace Event button twice to add the following events:
      • reaction_added
      • reaction_removed
    6. Click Save Changes at the bottom of the page.

Test

  1. Go add some reactions to messages in your Slack Workspace.
  2. Wait until 5 PM (or modify the timer to fire more frequently) to see the bot post to the #general Slack channel.

Try Stackery For Free

Gain control and visibility of your serverless operations from architecture design to application deployment and infrastructure monitoring.