Slack Webhook (Best Tutorial 2019)

Slack Webhook

What is Slack Webhook

Slack is a great tool for improving communication and increasing transparency in your business, organization, club, or group of friends. Slack reduces internal emails, allowing you to convert your jumbled inbox into channels that contain all your crucial information, in a more organized and easy-to-find manner. In other words, Slack can help you separate important work messages from office chatter. It also provides quick communication via instant messages to teammates, which you can’t do via email.

 

But Slack is much more than an email replacement; it can also be a central hub for all of your important services. It can be the one plat‐ form from which you access everything else in your daily workflow. With apps such as Trello, Google Calendar, and so many others, it’s a great place for your team to keep up-to-date with all aspects of your business.

 

Source code

Remember, the source code for all hacks in this blog can be found in this blog’s Github repo. This blog provides a walkthrough of how you can integrate Slack into your business. There are also a couple of hacks that are relevant to using Slack in the workplace.

 

Slack for Your Business

Before setting up and installing Slack for your team, make sure you know why you want to integrate it into your company or organization. Is it to reduce the volume of emails? Is it to increase communication between internal and remote workers? Is it to allow your team to see notifications from all of your services in one place? Whatever your reasoning, ensure you know exactly what problem you want to solve.

 

Once you’ve done that, ensure that everyone on the team is aware of what you’re trying to accomplish. Doing this allows people to be invested in solving the problem and to understand why they are being introduced to this new platform. You can’t force a workflow on people who aren’t invested. If Slack doesn’t solve the problem you identified, you may have to look for an alternative solution to your problem.

 

Making Your Business Slack Ready

Getting a team invested in a new technology is always difficult. People tend to be divided into two camps: those that are eager to try new things and those that are skeptical about changing. Convincing the former is easy, but convincing the latter takes time. If you’ve decided on Slack as your tool of choice (and a good choice it is) there are a few tips you can follow to make sure company roll- out goes smoothly.

 

Get it configured correctly - Take a few moments to go through the configuration options for your team. These can be accessed by clicking your team name in the top left of the Slack interface and then choosing the “Team Settings” option. For example, you might want to prevent some team members from adding integrations and apps, or you may wish to restrict the registration email to a specific domain.

 

Invite some beta testers - Use your fellow team members (or close colleagues) to try out the service. Slack recommends designating a day for this purpose.

 

Prohibit email - specify a trial period (for example a day or two) where all internal communications and questions must be sent via Slack, not email. This may seem a little over the top, but it gets people used to using the platform. Once people understand how Slack works and what it can be used for, they will know when to use Slack and when to revert to email.

 

Designate admins - Make sure the right people are admins. Pre-determine who should be in charge of advanced options and features before inviting everyone.

 

Roll it out gradually - If your beta users are happy and embracing Slack, roll it out the team by team - making sure you properly introduce the platform to them first - explaining how you expect them to use it and why they should. Just sending out an invite and expecting people to work it out themselves could upset people. Consider running a small presentation for each team to help them get used to Slack.

 

Make messages meaningful - Slack doesn’t just let you send simple messages - take a moment to read over the message for‐ matting tips, and pass them on to other them members to help emphasize certain words or separate out code blocks. You can find these tips in the Slack Help Center.

 

Remember that Slack allows more than just words - Think about installing some simple apps to help your team get their messages across. For example, Giphy allows Gifs to be sent with a simple keyword, and Growbot allows you to easily give praise to colleagues. Once your company is using Slack, you’re ready to start dividing the conversation and keeping chats on-topic using channels.

 

Channels

Slack uses channels to organize conversations and topics into separate rooms. Channel names and conversations can be seen by everyone on your organization’s team but to be notified or join in the conversation you need to join the channel. This allows team members to be involved only in conversations they want or need to be in.

 

When creating a Slack group, the temptation is to make lots of channels for every eventuality you can think of. The problem with this approach is people can get confused and be unsure where to post conversations, and conversations can get diluted.

 

Start off small, with only two or three channels, and only create extra ones when they’re requested and needed. For example, a #development channel might be required for your development team, and if the #general channel gets overwhelmed with talks of last night’s TV episodes, consider making an #entertainment channel.

 

If you are creating a project or team-specific channels, pick a naming convention and stick to it. For example, all project channels could start with #prj- and team channels could have a #t- prefix. This helps members identify the channel type, and also groups related channels together in the channel list.

 

Security

With an Instant Messenger at your fingertips, it’s very easy to use it to share private information between colleagues, such as passwords or login details. But keep in mind that anyone can potentially see and access the information you share on Slack (this includes any apps or integrations installed for the team). So think twice before sharing sensitive information.

 

You can also take steps to improve the security of Slack itself. As the team owner, you can force your team to reset their passwords. Do this by visiting:

Team Settings → Authentication → Forced Password Reset.

Now we’re ready to dive into some team hacks. The source code for all hacks in this blog can be found in this blog’s Github repo.

 

Email a reminder of a conversation

Although Slack can replace a lot of your need for email, there are some situations where you really need email, so here is a hack to help you integrate the two. For example, say someone outside of Slack asks you to perform a task, and you want to keep track of it in your inbox.

 

In this hack, you will create an HTML email that contains the last 10 Slack messages sent in a channel. Since this uses an Outgoing Webhook, it will only work in a public channel, and not a private conversation. This script will work by the user going to the relevant Slack channel and typing email me followed by a subject line such as email me Fix the web site bug.

 

This task could be accomplished using a slash command instead, but Outgoing Webhooks were chosen for this hack to provide transparency—webhooks lets other Slack users see that someone has requested an email transcript of the conversation. Before you get started, create a PHP file on a web-accessible server called email.php. This is the file we are going to trigger and use to send the email.

 

Create the Hook

After you’ve created and saved your PHP file, the next step is to set up an Outgoing Webhook. The settings below are just a guide (most of these settings can be overridden in the script itself):

  • Channel: Any
  • Trigger Word(s): !email me
  • URL(s): The URL where your script is hosted
  • Descriptive Label: Describe what the bot is doing
  • Customize Name: *Email Bot *
  • Customize Icon: Choose emoji and search for “email”; click the envelope emoji to select it.

 

Obtain an API Token

The script in this hack uses the Slack API to gather all of the information it needs. In order to interact with the API, you need to generate an OAuth token; here’s how:

  • 1. Go to https://api.slack.com/web.
  • 2. Scroll down to “Generate test tokens”.
  • 3. If you have previously generated a token it will be listed in the table on this page. If not, click the “Create token” button.
  • 4. Make a note of the token.

 

Script set up

With this hack, we are going to include the Slack PHP Library. This makes it easier to interact with the API and saves you from having to write your own cURL request. Download the Slack API PHP file from the GitHub repository linked to above, and place it in the same directory as the email.php script you created earlier.

 

Next, open the email.php script and paste in the following code. This code includes the Slack API library and initializes a new instance of the Slack class used later in this script.

<?php
// Include slack library from https://github.com/10w042/slack-api
include 'Slack.php';
// Initialise new instance
$Slack = new Slack('[API TOKEN]');

 

When Slack triggers a Webhook, it sends a payload containing information about where the Webhook was triggered from. This includes the channel_id of the channel and the user_id of the user who triggered it.

 

Using this payload data, which is sent as $_POST data to the PHP script, we can query the Slack API. This allows you to get the email address of the user who triggered the Webhook and the channel’s history. This will be used for the contents of the email.

In the code below, the channel history is loaded, and the user details are stored into a $me variable. Both the channel and user data are obtained by querying the Slack API. Lastly, the text that accompanied the email code (if there was any) is stored in a variable.

 

Place this code after your Slack API initialization:

/ Get the last 11 messages of the channel conversation
$data = $Slack->call('channels.history', array( 'channel' => $_POST['channel_id'],
'count' => 11
));
// Get the user who requested the email
$me = $Slack->call('http://users.info', array( 'user' => $_POST['user_id']
));
// Strip "emailme" from the message to get the subject line
// (if one was entered)
$text = str_replace('emailme', '', $_POST['text']);

 

The first variable, $data, is an array containing the last 11 messages in this channel, ranging from newest to oldest. This includes the one that triggered this Webhook. If you change this number, make sure you add an extra message to allow for the trigger message. The $me variable contains all of the data about the user, including their name, image, and (most importantly) email address.

 

Create a messages array

Using the array of messages that we retrieved from the channel history, we can create a new array containing the messages along with the names and pictures of the users who wrote them. (Currently, the messages array in the $data from the Slack API contains only a user ID.)

Insert this code at the end of your PHP script:

// Create empty array
$messages = array();
// Loop through channel messages
foreach($data['messages'] as $d) {
// Check If the message has a user - this excludes bot
// messages from the email
if($d['user']) {
// Get the user associated with the message
$userData = $Slack->call('http://users.info', array('user' \
=> $d['user']));
// Add to the messages array the name & image of the user,
// plus the actual message
$messages[] = array(
'user' => $userData['user']['real_name'],
'image' => $userData['user']['profile']['image_24'], 'message' => trim($d['text'])
);
}
}
// Remove the first item from the array (the trigger message)
array_shift($messages);
// Reverse the messages so oldest is first
$messages = array_reverse($messages);

The code above loops through the existing messages array and, if a user key is present in the message, adds it to the $messages array. If the user key is missing, this indicates it’s a message from a bot or other Webhook, which you can ignore.

Lastly, the trigger message gets removed (we don’t need it in the email) and the array gets reversed. Reversing the array means that, when we output the messages in the email, they will read from old‐ est to newest.

 

Specifying the Recipient and Subject

The first step in creating the email is to specify the recipient. To be sure that the name and email appear correctly in the email client’s To field, specify a name and an email address in this format: Full Name <email@address.com>.

Because we have data about the current user stored in the $me variable, we can easily construct the recipient. The subject line can also be set, either by taking the user input text or by setting a default one.

 

To accomplish these two things, add this code after the $messages array described in the previous section:

/ Create recipient

$to = $me['user']['profile']['real_name_normalized'] . ' <' . \

$me['user']['profile']['email'] . '>';

// Set the subject line

$subject = ($text) ? $text : 'Email reminder from Slack';

 

Creating the Email Headers

The next step is to set the headers for the email. The following code tells the email client how to handle the incoming email so it can be processed as HTML. It also sets the sender of the message. Add this code to the end of your PHP file:

 

// Set the headers & sender of the email

$headers = 'MIME-Version: 1.0' . "\r\n";

$headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";

$headers .= 'From: Slackbot <slack@yourdomain.com>' . "\r\n";

 

Specifying the Messages

Now that you’ve specified the email headers, you need to create variables for both the name of the user and a string of all the messages in a <table> element, which will be inserted into the HTML template.

 

Insert this code after the headers have been set:

// Set the name for the email
$name = $me['user']['profile']['first_name'];
// Create the messages table
$table = '<table width="100%" style="border-collapse:collapse">';
// Loop through all the messages
foreach($messages as $m) {
$table .= '<tr><td style="border:1px solid #ccc;padding:5px;\ width:24px"><img class='lazy' data-src="' . $m['image'] . '" \ style="display:block"></td><td style="border:1px \ solid #ccc;padding:5px"><b>' . $m['user'] . '</b> \
</td><td style="border:1px solid #ccc;padding:5px"> \
<i>' . $m['message'] . '</i></td><tr>';
}
// Close the table
$table .= '</table>';

 

HTML Email

To build the HTML email, we are going to use the PHP Heredoc syntax. This allows you to construct HTML and insert PHP variables without needing to escape them. The full HTML is too long to include in this blog, but you can download it from this blog’s Github repository. Here’s an example of Heredoc in use:

$message = <<<HTML Hello {$name}.<br><br>
You requested an email of the conversation.<br><br>
{$table} HTML;
Send the email
We can now use PHP to send the email since we have the recipient, subject, message, and headers. Add the following to your PHP file:
// Send the email!
mail($to, $subject, $message, $headers);

 

Notify the User via Slackbot

The last thing this script does is notify the user through Slackbot that the email has been sent. With the Slack API, if you post a message to a channel that has the same ID as a user ID, it will appear in the Slackbot private conversation. We are going to use this ability to private message a user to send a message informing them the email has been sent.

 

Add this to the end of the PHP file you’ve been working on:

// Post a message in the Slackbot channel
$sendMessage = $Slack->call('chat.postMessage', array( 'channel' => $me['user']['id'],
'text' => 'Yo ' . $me['user']['profile']['first_name'] . ' \
- I\'ve sent you that email you asked for', 'username' => 'Emailbot',
'icon_emoji' => ':email:'
));

You now have an easy way to send recent Slack messages via email.

 

Celebrate unusual holidays

In this hack, we will create a notification to inform you when there are unusual holidays. For example, did you know that the 20th of January is Penguin Awareness Day? Or that the 15th of September is Make A Hat Day?

 

Create an Incoming Webhook

This script uses Incoming Webhooks to post the message to Slack. Add an Incoming Webhook by visiting the Apps and Integrations app directory, searching for Incoming Webhooks and then clicking install. Finally, select a channel that you want the holiday messages to be posted to, and click Add Incoming Webhook.

Once installed, you will be shown a Webhook URL. Make a note of this URL, because you’ll use it to post a message to your organization.

 

Collate your days

The next step is to build a list of days to shout about. This can be a personal collection of dates (birthdays and national holidays, say) or you can include unusual dates. Create a PHP file called dates.php on a server. This is the file we’ll be editing and adding the calendar to.

 

To build up a calendar, create 12 arrays inside an array, with numbers 01 → 12 as the keys, which will be the months. (For the sake of readability, make sure the numbers 1-9 are preceded with a 0.) Inside each month, create a key for each day of that month. You can either do this yourself (using the example below) or you can simply download this template from Github.

 

To keep the codebase clean, the dates have been saved in a separate file from the rest of the code and assigned to the $dates variable - this file will be included in our main PHP script in the next step.

 

The text input can include both Slack formatting (for example, sur‐ round the text in * to make the text bold) and emojis. The emojis can be either the text representation (: cactus:), or the actual symbol - both will be displayed correctly by Slack. Now that you’ve created your list of dates, create a second PHP file titled generate-day.php in the same folder as dates.php and include your list of dates in it, like so:

<?php

// Include the dates

include './dates.php';

 

Get today’s day

Now that you have a list of days, you need to retrieve info for the current date. To do that, create a new DateTime() object in your generate-day PHP file after the include. This will default to the current date if nothing is passed in.

// Get todays date
$today = new DateTime();
You can now query the $today object to get the current month and day with a leading zero to match the keys on the arrays.
// Get the current month with a leading zero
// echo $today->format('m')
// Get the current day with a leading zero
// echo $today->format('d')
// Get the special day!
$day = $dates[$today->format('m')][$today->format('d')];

 

Post to Slack

Before posting to Slack, let’s ensure that there is something to post (you don’t want to send an empty message). Add this to your generate-day PHP file:

// If today has a value

if(count($day) > 0) {

// Post to Slack

}

The next step is to encode the data as JSON and assign it to the pay load variable that Slack expects. We’ve chosen to title the bot What "international" day is it today? and have assigned it the :rosette: icon_emoji to add a little bit of celebration to the day. The text in the message is then simply the actual name of the day:

 

// Encode the data

$data = 'payload=' . json_encode(array(
'username' => 'What "international" day is it today?', 'icon_emoji' => ':rosette:',
'text' => $day,
));
Finally, set the $webhook_url (to post to) and include the PHP cURL code.
// Your webhook URL
$webhook = '[WEBHOOK URL]';
// PHP cURL POST request
$ch = curl_init($webhook);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch); curl_close($ch);
echo $result;
Your full generate-day.php file should look like the code below. Make sure this file is placed on a server that can run PHP scripts.
<?php // Include the dates include './dates.php'; // Get todays date $today=new DateTime(); // Get the special day! $day=$dates[$today->format('m')][$today->format('d')];
// If today has a value
if(count($day) > 0) {
// Encode the data
$data = 'payload=' . json_encode(array(
'username' => 'What "international" day is it today?', 'icon_emoji' => ':rosette:',
'text' => $day,
));
// Your webhook URL
$webhook = '[WEBHOOK URL]';
// PHP cURL POST request
$ch = curl_init($webhook);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $data); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch); curl_close($ch);
echo $result;
}

Try out the script by navigating to the URL of your script. The web page should say “ok” and your message should be posted to Slack.

 

Trigger the Script Daily

The last step of this hack is to trigger the script on a daily basis using a cronjob. This can be done either through a control panel (for example cPanel) if you have one, or through the command line. We explain both approaches in the following sections.

 

Control Panel

The following instructions are for setting up a cronjob using cPanel —other control panels may be slightly different. Log into your cPanel and navigate to “Cron Jobs”. You should be presented with a series of drop-down options. If there is a “common settings” or “presets” option, select “Once a day” from it. This should pre-populate the fields.

 

If such an option isn’t present, use the following settings instead:

  • Minute: 0 (this runs at 0 minutes every hour triggered)
  • Hour: 9 (this will trigger at 9 am every morning)
  • Day: * (run this every day)
  • Month: * (run this every month)
  • Weekday: 1-5 (Only run this Monday-Friday - you don’t notifications at the weekend!)

In the command box, enter the following:

 

PHP /path/to/generate-day.php >/dev/null 2>&1

The PHP in this line of code tells the cronjob to run the script like PHP, and the path is the one to your PHP script. Lastly, the >/dev/null 2>&1 tells the cronjob not to output or email any reports to the user.

 

Command Line

Cronjobs can also be set up on the server using the command line. If you don’t have access to the command line for your server or you are not comfortable doing this, ask someone who is familiar with setting up recurring tasks to help you, or use the control panel option instead.

The next step is to create the cronjob. Remember that the time this triggers will be based on the local server time. Check your server time by typing date on the command line.

To edit the cronjob file, type:

sudo crontab -e

 

This will open a file. Add the following line of code to the bottom of that file:

0 9 * * 1-5 PHP /path/to/generate-day.php >/dev/null 2>&1

 

These options and commands are very similar to that of the control panel version, and so should look familiar.

You’ll now receive a little Slack message every day (when appropriate) that lets you know what you should be celebrating.

 

Add Hacker News Updates

In this hack, you’ll use an API to post news items to Slack. We’ll use an API because, although Slack can handle RSS feeds, it doesn’t provide control over the output, and some sites don’t offer an RSS feed. We’re going to retrieve articles from Hacker News. The official Hacker News API requires a lot of requests to piece together the required information, so we will use the API provided on algo‐ http://lia.com.

 

The script we’re going to create is written in PHP and runs a cron job every minute. It uses the provided API to get the latest story. If the story is dated after the last stored date, it will post to Slack and update the stored date to that of the most recent post.

 

This isn’t a bulletproof method, because if two (or more) stories are posted within a minute of each other, this script will post only one story. But it should work well enough.

The API endpoint we are going to use is:

http://hn.algolia.com/api/v1/search_by_date?tags=story

 

Create an Incoming Webhook

This script uses Incoming Webhooks to post messages to Slack. Add an Incoming Webhook by visiting the Apps and Integrations app directory, searching for Incoming Webhooks and then clicking install. Select a channel that you want the messages to be posted to and then click Add Incoming Webhook.

Once installed, you will be shown a Webhook URL. Make a note of the URL, since you’ll use it to post messages.

 

Gather the data

The next step is to gather the data from the feed. The Hacker News API allows you to specify several parameters to limit the number of items you get, which in turn speeds up the request.

Create the PHP script on a server web-accessible called feed.php. This will host all of the code. At the beginning of the file, specify the URL of the feed.

<?php

// Feed URL

$feed = 'http://hn.algolia.com/api/v1/search_by_date?';

Next, create a $lastDate variable. This will start at 0, but will be updated every time the script runs to store the date of the last item retrieved. You can then build a new request and get any new articles.

// Set a last collected date - this will be dynamically updated later

$lastDate = 0;

 

Now, build up the parameters for the API. PHP includes a function that can take an array and transform it into a URL encoded string: http_build_query(). (More information about this function can be found on the PHP Docs page). Add the following code to feed.php:

// Parameters
$params = array(
// Only retrieve stories (not polls or comments)
'tags' => 'story',
// Get any stories created after the lastDate integer
'numericFilters' => 'created_at_i>' . $lastDate,
// Only return 1 item
'hitsPerPage' => 1
);

 

// Build the full path using http_build_query

$url = $feed . http_build_query($params);

Once you build the $url you can retrieve it using PHP’s file_get_contents(). The feed will be in JSON format, which can easily be converted to a PHP object by wrapping json_decode() around the file request. To retrieve the feed and decipher it, add the following code to feed.php:

// Get the API data

$data = json_decode(file_get_contents($url));

Next, add the following code to check to see whether there are any new stories. If not, you will exit the script (there is no point in continuing).

 

// Check to see if there are any new stories

if(!count($data->hits)) exit('No stories');

You want to post the latest story to Slack, so to make accessing all of the information easier, assign a new variable ($story) to the first item in the hits array in your PHP file:

// Isolate the story

$story = $data->hits[0];

 

Format the story’s date

Before posting the data to Slack, you should format the date of the story, making it more human readable. Currently, the data exists in a Unix timestamp format also known as ISO_8601. Using PHP, you can take the timestamp and convert it to something more easily understood by the DateTime PHP class. Here is an example of how to do that:

// Format the date - pass an @ if using timestamp

$date = new DateTime('@' . $story->created_at_i);

echo $date->format('jS F Y g:ia');

// returns date in format of:

// 1st January 1970 12:01am

 

When using the DateTime class with a Unix timestamp, an @ must be passed first, to allow the code to understand the input.

 

Build the payload for Slack

Now that we have a formatted date, we can proceed with formatting the payload data for Slack. In this block of code, we parse the Date using DateTime() and then build up an array of information ready to be posted to Slack. Add this code to your PHP file:

// Format the date - pass an @ if using timestamp
$date = new DateTime('@' . $story->created_at_i);
// Create fields array
$fields = array(
array('title' => 'Title', 'value' => '<' . $story->url . '|' . $story->title . '>'),
array('title' => 'Date', 'value' => $date->format('jS F Y g:ia'), 'short' => true),
array('title' => 'Author', 'value' => $story->author, 'short' => true)
);
// Conditional field if story_text is present (strip any HTML tags)
if($story->story_text != null) {
$fields[] = array('title' => 'Story text', 'value' => strip_tags($story->stor
}
// Conditional field if comment_text is present (strip any HTML tags)
if($story->comment_text != null) {
$fields[] = array('title' => 'Comment text', 'value' => strip_tags($story->co
}
// Encode the data
$payload = 'payload=' . json_encode(array(
// Username and nice icon 'username' => 'Hacker News', 'icon_emoji' => ':fax:',
// Required fallback and some pretext
'pretext' => 'A new story from Hacker News', 'fallback' => 'New hack news story - ' . $story->url,
// Hacker news orange
'color' => '#ff6600',
// Title as a link, date and author of the news story
'fields' => $fields
));

 

Before building the payload, the $fields array is predefined and conditionally appends the story_text and comment_text if it is present. We’ve decided to attach the news as a field attachment because doing so gives you more control over the formatting and the color (in this example, it has been set to Hacker News orange).

 

The title of the story has been passed in as a link. This uses the Slack link format of <URL|Link text>. Note the vertical pipe | between the URL and text. The username and icon are also set in the code; we went for the fax emoji to signify a new story.

 

Lastly, we need the PHP cURL request, so add this to the end of your file:

// PHP cURL POST request

$ch = curl_init($webhook);

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$result = curl_exec($ch); curl_close($ch);

 

Retrieving new items

As mentioned before, the feed URL can be passed an additional parameter in the form of a UNIX timestamp so that it retrieves sto‐ ries from a specific date and time. If you use this parameter, it will improve the speed of the HTTP request, because it restricts the number of items the feed needs to return.

 

To achieve this, the script needs to create and use a temporary file to store the timestamp of the last story it processed. PHP includes the ability to read, write and create other files in the filesystem. To do this, it uses the following functions:

 

file_get_contents() to retrieve the data from a file

file_put_contents() to write data to a file. Add this code to the very top of your feed.php file:

// Specify path to Temp data file
$file = 'date.txt';
// Set a last collected date
$lastDate = file_get_contents($file);
if(!$lastDate) {
$lastDate = 0;
}

 

Here, we specify the path to the temporary file. If this file doesn’t exist, PHP returns false, in which case the $lastDate variable gets set to 0. If the file is present with contents, the $lastDate gets assigned to the temporary file, which should be a UNIX timestamp. At the very end of the PHP file, add the code below. It stores the timestamp of the processed story in the temporary file for the next time the script runs.

file_put_contents($file, $story->created_at_i);
The final feed.php script
Now that we’ve saved the last processed article and are constantly updating the temporary file, the webhook file is complete:
<?php // Your webhook URL $webhook='[WEBHOOK URL]'; // Feed URL $feed='http://hn.algolia.com/api/v1/search_by_date?'; // Specify path to Temp data file $file='date.txt'; // Set a last collected date $lastDate=file_get_contents($file); if(!$lastDate) { $lastDate=0; } // Parameters $params=array( // Only retrieve stories (not polls or comments) 'tags'=> 'story',
// Get any stories created after the lastDate integer
'numericFilters' => 'created_at_i>' . $lastDate,
// Only return 1 item
'hitsPerPage' => 1
);
// Build the full path using http_build_query
$url = $feed . http_build_query($params);
// Get the API data
$data = json_decode(file_get_contents($url));
// Check to see if there are any new stories
if(!count($data->hits)) exit('No stories');
// Isolate the story
$story = $data->hits[0];
// Format the date - pass an @ if using timestamp
$date = new DateTime('@' . $story->created_at_i);
// Create fields array
$fields = array(
array('title' => 'Title', 'value' =>
'<' . $story->url . '|' . $story->title . '>'),
array('title' => 'Date', 'value' =>
$date->format('jS F Y g:ia'), 'short' => true), array('title' => 'Author', 'value' =>
$story->author, 'short' => true)
);
// Conditional field if story_text is present (strip any HTML tags)
if($story->story_text != null) {
$fields[] = array('title' => 'Story text', 'value' => strip_tags($story->
}
// Conditional field if comment_text is present
if($story->comment_text != null) {
$fields[] = array('title' => 'Comment text', 'value' => strip_tags($story
}
// Encode the data
$payload = 'payload=' . json_encode(array(
// Username and nice icon 'username' => 'Hacker News', 'icon_emoji' => ':fax:',
// Required fallback and some pretext
'pretext' => 'A new story from Hacker News', 'fallback' => 'New hack news story - ' . $story->url,
// Hacker news orange
'color' => '#ff6600',
));
// Title as a link, date and author of the news story
'fields' => $fields
// PHP cURL POST request
$ch = curl_init($webhook);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch); curl_close($ch);
file_put_contents($file, $story->created_at_i);
echo $result;

 

Trigger the Script every Minute

The final step is to trigger the script on every minute using a cron‐ job. This can be done either through a control panel (for example cPanel) if you have one, or through the command line. We explain both methods below.

 

Control Panel

Log into your cPanel and navigate to “Cron Jobs”. You should be presented with a series of drop-down options. If there is a “common settings” or “presets” option, select “Once a minute” from it. This should pre-populate the fields.

 

If such an option isn’t present, use the following settings instead:

Minute: * (Every minute)
Hour: * (Every Hour)
Day: * (Every day)
Month: * (Every month)
Weekday: 1-5 (Every Weekday)

 

In the command box, enter the following:

PHP /path/to/feed.php >/dev/null 2>&1

The PHP in this line of code informs the cronjob to run the script like PHP, and the path is the one to your PHP script. The >/dev/null 2>&1 bit tells the cronjob not to output or email any reports to the user.

 

Command Line

Cronjobs can also be set up on the server using the command line. If you don’t have access to the command line for your server or aren’t comfortable doing this, find someone who is familiar with setting up recurring tasks and ask for their help, or use the control panel option instead.

 

The first step is to create the cronjob. Remember that the time this triggers will be based on the local server time. (You can check your server time by tying date on the command line.)

 

To edit the cronjob file, type: sudo crontab -e

This will open a file. Add the following line of code to the bottom of that file: 0 9 * * 1-5 PHP /path/to/generate-day.php >/dev/null 2>&1

These options and commands are very similar to that of the control panel version (described above), and so should look familiar. You should now get the latest hacker news story every minute, every weekday!

 

Output your team’s timezones

If you work with a team that’s spread around the globe, you want to be sure that you don’t try to contact someone in the middle of the night their time. This hack will show you how to create a handy slash command that outputs the current times and timezones of all the users in a given channel.

 

To get started, create a PHP file called timezones.php on a web-accessible URL. This script will be triggered by a slash command. In the same folder as this script, download the slack-API PHP wrapper.

 

This script is best for small teams or channels as it requires querying the Slack API several times. Set up the slash command and auth token Navigate to the Slack apps and integrations website and, using the search box at the top of the page, search for “Slash commands”. Click the first result titled “Slash commands,” and then click the “Add con‐ figuration” button on the left-hand side.

 

In the “Choose a Command” box, enter something appropriate, such as /times. On the following page, input the URL to your time zones.php script into the URL box. You can customize the look of your slash command using the “Customize Name” and “Customize Icon” fields using the settings below:

 

Customize Name: Timezones

• Customize Icon: Click “Choose an Emoji” and search for the clock emoji.

Next, create an OAuth token, since you’ll have to query users and channels to gather the information needed for this script.

 

Obtain an API Token

This script uses the Slack API to gather the info it needs. In order to interact with the API, you need to generate an oAuth token:

  • 1. Go to https://api.slack.com/web.
  • 2. Scroll down to “Generate test tokens”.
  • 3. If you have previously generated a token, there will be one near the top, with the team name on the left. If not, click the “Create token” button.
  • 4. Make a note of the token.

 

Script Set Up

There is a small amount of set-up we need to do for this hack. Since we are dealing with timezones, you need to make sure that the script uses UTC as the base time, regardless of what the server is set to. To do that, add the following code to your timezones PHP script:

<?php
// Your oAuth Token
$token = '[AUTH TOKEN]';
// Set the timezone independent of the server
date_default_timezone_set("UTC");
// Get the channel ID the script was triggered from
$channelID = $_POST['channel_id'];
// Include slack library from https://github.com/10w042/slack-api
include 'Slack.php';
// Create new Slack instance
$Slack = new Slack($token);

 

Now that the Slack instance is initialized, we can query the Slack API to get a list of all of the members of the channel the script was triggered from using the payload Slack sends when triggering Webhooks:

// Get the current channel and user

$channel = $Slack->call('http://channels.info', array(channel' => $channelID));

 

After you have the channel information, you need to get the current time as a Unix timestamp. You also need to create an empty array (you’ll use it later). To accomplish both these things, add the follow‐ ing to your timezones file:

/ Get the time right now
$now = time();
// Create an empty array
$times = array();

 

We used the time() function here instead of the date() function because, unlike the date() function, the time() function doesn’t require any further parameters to get the timestamp. If you used the date() function instead, your preferred format would need to be passed as additional parameters.

 

Loop through the channel’s users

Now that you have information about the channel, you can loop through each user on that channel and create a user instance based on the user’s ID. Once you have the user instance, you can then get the timezone and name of the user. To do that, append the following code to your PHP file:

// Loop through channel members

for each($channel[channel']['members'] as $mid) {

// Create member instance with ID

$user = $Slack->call('http://users.info', array('user' => $mid));

}

 

Chances are (because you’re reading this blog) that you have at least one bot in your Slack team. We only really want to see timezones of real team members, so the code below excludes bots from the output of your script. Add this into the foreach loop above, after the http://users.info Slack API call.

// if the user is a bot, or doesn't have a timezone offset, skip them

if(($user['user']['is_bot'] == true) || \ (!isset($user['user']['tz_offset']))) {

continue;

}

 

This code checks whether the is_bot flag is set to true or the time‐ zone offset is missing from the profile. If either (or both) of those conditions are true, then it fires continue. In the instance of a loop, the code will skip the remainder of the current code and move onto the next user. Once you are happy that you have an actual user, there are a few variables to calculate.

 

We need to work out each user’s time difference (in hours) compared to UTC, so we can output it at the end of the message. In the Slack API, the tz_offset is set in seconds, so dividing it by 60 twice gives us the figure we need. Add the following code after the if statement that’s inside the foreach loop:

 

// Work out offset in hours from seconds

$userOffset = ($user['user']['tz_offset'] / 60) / 60;

 

Next, we want to get the user’s name. If someone has filled out their profile correctly, the following code will grab their full name; if not, it will go with their username, so at least we have an identifying title. Add the following code after where the $userOffset variable is declared:

// Get the name of the user

$name = ($user['user']['real_name']) ? $user['user']['real_name'] : \

$user['user']['name'];

 

In order to work out the current “local time” for the user, the follow‐ ing code adds the timezone offset seconds to the current timestamp. This provides a timestamp that you can format later. Add this code after where the $name variable is set:

// Add the timezone offset to the current time

$userTime = $now + $user['user']['tz_offset'];

 

The last variable we need to set is $key. We are appending all of the users to an array, and we want them listed in time order. By making the key of the array the same as the offset, we can ensure they will be in the correct order. Because a PHP can’t have negative arrays, the following code adds 12 to every user. -11 is the lowest the offset can be, so this ensures it will always be a positive number. Use the code below to set the $key variable—place it after the $userTime variable.

// Create an array key based on offset (so we can sort) and add 12
// (as it could be -11 at worst)
$key = $userOffset + 12;
Now that we have all of the variables we need, we can build the string for the user. Add this code inside the foreach loop:
// Append the details to the array as the key
$times[$key][] = '*' . $name . '*: ' .
date('h:i A', $userTime) . ' local time on ' . date('jS M', $userTime) . ' (UTC' . sprintf("%+d", $userOffset) . ' hours _' . \
$user['user']['tz_label'] . '_)';

 

The string is being appended to the $times array, with the correct key. To allow for cases where two users are in the same timezone, we create a child array to hold them using the empty square brackets [] after the [$key].

 

The string outputs the following text (with various formatting):

$name - the name of the user.
date('h:i A', $userTime) - the hour & minutes in a 12 hour clock (e.g. 12:01 AM).
date('jS M', $userTime) - the day and month of the user’s local time in the instance that any of the users are on a different day to the rest of the team (e.g. 1st Jan).
sprintf("%+d", $userOffset) - This outputs the timezone offset with a + prepended if it is positive (e.g. +2 hours).
$user['user']['tz_label'] - This outputs the label for the user’s timezone (e.g. Pacific Daylight Time).

A populated user string looks like this:

Joe Blogs: 12:01 PM local time on 1st Jan (UTC-7 hours Pacific Day‐ light Time)

Pieced together, the entire user foreach loop now looks like this:

// Loop through chanel members
foreach($channel['channel']['members'] as $mid) {
// Create member instance with ID
$user = $Slack->call('http://users.info', array('user' => $mid));
// If the user is a bot, or doesn't have a timezone offset, skip them
if(($user['user']['is_bot'] == true) ||
(!isset($user['user']['tz_offset']))) {
continue;
}
// Work out offset in hours from seconds
$userOffset = ($user['user']['tz_offset'] / 60) / 60;
// Get the name of the user
$name = ($user['user']['real_name']) ? $user['user']['real_name'] : \
$user['user']['name'];
// Add the timezeone offset to current time
$userTime = $now + $user['user']['tz_offset'];
// Create an array key based on offset (so we can sort) and add 12
// (as it could be -11 at worst)
$key = $userOffset + 12;
// Append the details to the array as the key
$times[$key][] = '*' . $name . '*: ' .
date('h:i A', $userTime) . ' local time on ' . date('jS M', $userTime) . ' (UTC' . sprintf("%+d", $userOffset) . ' hours _' . \
$user['user']['tz_label'] . '_)';
}

 

Processing the data

Now that we have a multidimensional array of $times with all of the users’ timezones stored inside, we need to process the data to make it ready for Slack. To do that, add this code after the foreach loop:

// Sort array items by key

ksort($times);

// Flatten the array and implode it - separated by new lines

$text = implode("\n", call_user_func_array( 'array_merge', $times

));

 

The first function sorts the array by keys (such as smallest to big‐ gest). This means that people with a timezone of -10 (and a key of 2, since we added 12) come before people in timezone +3 (key of 15).

 

The next line helps flatten the array and convert it to a string. Currently, the $times array looks like this:

$times = array( 2 => array(
’Joe Blogs:...’,
'John Doe...'
),
15 => array(
'Jane Doe...'
)
);

 

The keys were there purely for ordering purposes, so the array could be flattened. Calling the call_user_func_array function with array_merge does exactly that. After passing it through this series of functions, the array would become:

$times = array(

’Joe Blogs:...’, 'John Doe…', 'Jane Doe...'

);

As Slack expects a string, we can implode() that array, using the newline character \n as the “glue”. You can now trigger the script with the slash command you created (/times in this example), and get a list of all the local times for users in that channel.

 

Slack automatically notifies a user if their name is mentioned in a channel. This script mentions everyone’s names so it is likely that they will get a notification when the script is called. Also, because the script is doing several API calls, the more people in the channel, the longer it will take to return results.

 

Connect Slack to Trello

Trello is a collaborative project management tool. It helps teams and groups sort bugs and todos, based on the kanban board method. It incorporates the ideas of lists and cards. A card is often a task and is contained in a list (such as To-do or Done).

 

This hack will whos you how to create a new card on a specified Trello board using Outgoing Webhooks and the last message sent in a Slack channel. This same thing can be achieved using Zapier, but by using this custom hack instead of Zapier, you have control over the card’s formatting, where it goes, and which board in Trello it goes on.

 

The process for this script will be:

1. The user initializes the script by typing add to trello <board name>.

2. The script gathers the last message sent in that Slack channel.

3. The script works out what board the user mentioned. If the board doesn’t exist or the script can’t find it, it will use the default board.

4. A card is created in the first list of that board.

5. A message is returned with a link to the board.

For this hack, you will need a Slack API key as well as a Trello key and token.

 

For the Trello credentials, we advise you to make a new autonomous user account and add it to all the boards. For example, create an account called Jeeves or Jarvis who can post to all the boards required.

 

Create the Outgoing Webhook

First, you need to set up an Outgoing Webhook. The settings below are just a guide. Most of these settings can be overridden in the script itself.

Channel: Any

  • Trigger Word(s): add to trello
  • URL(s): The URL where your script is hosted
  • Descriptive Label: Describe what the bot does
  • Customize Name: Trello Bot
  • Customize Icon: Whatever you wish

 

Obtain a Slack API Token

This script uses the Slack API to gather the information it needs. In order to interact with the API, you need to generate an OAuth token:

1. Go to https://api.slack.com/web.

2. Scroll down to “Generate test tokens”.

3. If you have previously generated a token, there will be one near the top, with the team name on the left. If not, click the “Create token” button.

4. Make a note of the token.

 

Obtain a Trello Key and Token

The Trello API allows a logged in user to get a key and token, which allows access to all of that user’s boards and cards. To get a key and token:

  • 1. Go to https://trello.com/app-key
  • 2. Copy the developer key on that page
  • 3. Click here to generate a token
  • 4. When you’re asked to allow the app use your account, click Allow
  • 5. Copy the token that’s displayed

 

Default Board ID

The last thing you need before you write your script is the ID of the default Trello board. This is the board that gets posted to if the script can’t find the requested board or if not board is specified. You can get this info by navigating to the board and copying its ID from the URL. For example, you’d copy whatever appears in place of [ID] in the URL below - it will be some numbers and letters:

https://trello.com/b/[ID]/testing

 

Now that you have the required keys and tokens, let’s start the script. On a server, create a PHP file called trello.php, and then enter the following code into it. This code specifies the Slack and Trello tokens, Trello key, and the default board ID:

<?php
// Your Slack oAuth Token
$slack_token = '[Slack auth token]';
// Trello
// https://trello.com/app-key
$trello_key = '[Trello key]';
$trello_token = '[Trello token]';
$trello_default_board = '[Default board id]';
// Trigger Word
$trigger_phrase = 'add to trello';

 

We have specified the trigger word for the file. Make sure this matches what you entered when you created your Webhook so that you can remove the trigger word when you add the card to Trello.

 

Libraries

  • There are some PHP wrappers available that make using the Slack and Trello APIs easier.
  • The first one is the Slack PHP API wrapper. Download this script and place it next to your PHP script on the server.
  • The second is php-trello. Download this zip and place in a folder called trello in the same directory as your PHP script.
  • Include the libraries in your PHP file by adding the code below. The Trello wrapper requires you to specify the namespace.
// Include slack library
include 'Slack.php';
// Include Trello API helpers
include 'trello/class='lazy' data-src/Trello/OAuthSimple.php';
include 'trello/class='lazy' data-src/Trello/Trello.php';
// Set Trello namespace
use \Trello\Trello;

 

With these libraries loaded, you can initialize the Trello and Slack classes with the tokens you obtained earlier. To do so, insert the following code into trello.php:

// Create new Slack & Trello instance

$Slack = new Slack($slack_token);

$Trello = new Trello($trello_key, null, $trello_token);

Process the Slack channel data

 

You need to get the current Slack channel’s history, to get both the message that triggered the script (as it contains the relevant board’s name) and the message you wish to add to Trello as a card.

 

Because some of the messages posted in Slack aren’t user messages, retrieve the last 5 messages in a channel to ensure the board that gets added is the one requested. To do that, add this to trello.php:

// Get the last 5 messages of the channel conversation

$slack_data = $Slack->call('channels.history', array( 'channel' => $_POST['channel_id'],

'count' => 5

));

With the channel messages now available as a PHP variable, you need to remove the most recent one (this is the message that triggered the script). From there, we are going to remove the trigger phrase so we are left with the name of the board the user wishes to post to. Add the following to your PHP script after the Slack API call:

// Remove the last message - it will contain the trigger phrase
$slack_trigger_message = array_shift($slack_data['messages']);
// Remove trigger_phrase from message to get board name
$slack_board_name = trim( str_replace(
strtolower($trigger_phrase), '',
strtolower($slack_trigger_message['text'])
)
);

 

For the final Slack message processing stage, we are going to loop through the remaining messages until we find one with a type equal to the message. Once we do (it will generally be the first one), we are going to assign it to a variable and break out of the loop. The message also gets processed to remove < and > from any URL. Add the following code after where the $slack_board_name variable is defined:

// Find the next message in the channel - strip URL formatting

foreach($slack_data['messages'] as $message) {
$message['text'] = preg_replace("/\<(http[^ ]+)>/", "\\1", $message['text']);
if($message['type'] == 'message') {
$trello_card = $message;
break;
}
}

 

Load the Trello board

In the next stage, you’ll load the Trello board to post to it. To do this, you must use the API to load a message to an authenticated Trello user, get their open boards, and loop through them until you find one that matches. If there is no board specified (or matching), the default board will be loaded.

 

The code that decides whether the board matches simply checks whether a string is contained within another string. For example, if you type to add to trello m, the script will add the card to the first board it finds with an M in its name.

 

Add this code to your trello PHP script:

// Get the trello user
$trello_user = $Trello->members->get('me');
// Get all the boards the user has access to that are open
$trello_boards = $Trello->members->get(
$trello_user->id . '/boards',
array(
'filter' => 'open'
)
);
// Find the board that matches the message text
foreach ($trello_boards as $board) {
if(strpos(
strtolower($board->name),
$slack_board_name
) !== false) {
$trello_board = $board;
break;
}
}
// if there is no board - fall back to the default one
if(!$trello_board) {
$trello_board = $Trello->boards->get($trello_default_board);
}

 

Pick the Trello list

Now that you have the Trello board selected, you want to load the lists contained within that board, and then select the first list on that board. (This will be the list you add the card to.) To accomplish these two things, add the following to the end of your script.

// Get all the lists in the board
$trello_lists = $Trello->boards->get(
$trello_board->id . '/lists'
);
// Get the first list of the board
$trello_list = $trello_lists[0];

 

Create the Trello card

With the board and list selected, the last thing you need to do is to build the card and post it. We want to add a bit of context to the card, including the name of the person who posted the note and the time they did so. Since we have the card title and details loaded in the $trello_card variable, we can load the user based on who posted it.

 

Because people aren’t perfect, not everyone will have filled in their profile and has the real_name field populated. In the code below, we set the name to the name key, and override this if real_name exists. Insert this code at the bottom of trello.php:

/ Get the Slack user who noted the bug
$slack_user = $Slack->call('http://users.info', array( 'user' => $trello_card['user']
));
// Get the name of the user - if there is a real_name, use that
$slack_name = $slack_user['user']['name'];
if(count($slack_user['user']['real_name'])) {
$slack_name = $slack_user['user']['real_name'];
}

 

Now you have all of the info you need to add the card to Trello. The list is loaded (with the ID we got before) and the card added. We set the title of the card (or name) to the same as the Slack message and populated the description (desc) with the name and date of the request. To post the card to Trello, simply add this bit of code to trello.php:

// Post the card to trello

$Trello->lists->post($trello_list->id . '/cards', array( 'name' => $trello_card['text'],

'desc' => 'Requested by **' . $slack_name .

'** on ' . date('jS F', $trello_card['ts'])

));

 

Report back to the user

The last thing you need to do is let the user know that the card was posted successfully. As a nice addition, the board the card was added to is linked in this return message, so the user(s) can jump straight to it. The link is created using Slack’s <link url|text> syntax:

// Post back to board it was posted to header('Content-Type: application/json'); echo json_encode(array(

'text' => 'Added the card to the <' .

$trello_board->shortUrl . '|' . $trello_board->name . '> board'

));

You’re all set. You can now add a card to a Trello board by typing add to trello followed by the board name.

 

Although this script replicates some aspects of the Zapier app, it opens the gateway for further expansion. For example, you can:

  • Add custom labels to the card (to indicate that it’s from Slack).
  • Include a link to the message (to get chat context) by removing the . from the message timestamp and using the message name.
  • Keep a key/value list of channels to boards so that, when the script is run in a certain channel, the resulting card always goes to a particular board.
  • Set aboard and use the text after the command to set the message title.
  • Include the last 5 or so messages of the conversation in the Trello card.

 

Creating Chatbots

In this blog, you will learn to install a “full” chatbot. Unlike the chatbots covered earlier in this blog, you can extend the functionality of full chatbots using an API that is native to the language you extend the bot in. (For instance, the Errbot chatbot that we use in this blog is written in Python, so you can extend its functionality using Python.) Full chatbots are also software frameworks with many features allowing you to react to Slack events, mentions, custom presentations, and more.

 

Installing a full bot in Slack gives you access to a complete plugin ecosystem that has been built over time. Another advantage of using a full chatbot is that once you have set it up, you just need code to extend it; you don’t have to deal with repeated deployment procedures like you do with apps and slash commands.

 

Chatbots also abstract you from the chat system that you are using. So for example, Errbot (one of the chatbot we will explore) has a plugin method called send_card that sends a native message attachment on Slack. If you connect your instance to Hipchat it will use Hipchat’s native card rendering! This also applies to identity management, callbacks, a rendering language (markdown), etc. This means that you can publish and share your plugins and they should work out of the box for other users on other platforms.

 

Source code

The source code for all the hacks in this blog can be found in this blog’s Github repo.

We will start with Errbot, an easy chatbot to extend in Python, and tour the capabilities of the bot with a series of examples you can use for your team. We will then discuss some other popular chatbots: Hubot and Lita, which are extensible in CoffeeScript and Ruby, respectively.

 

And in the last portion of this blog, we cover another specialized Slack chatbot called Simple Slack API, which is open source software written in Java and freely available on Github. It allows Java (or other Java VM language) developers to quickly build a fully functional Slack bot running as a standalone application.

 

Let’s get started.

Connect Errbot to Slack

Let’s set up Errbot so you can take advantage of the hacks in this blog that use it. As mentioned earlier, Errbot is a chatbot that is written and is extensible in Python. Its homepage and documentation is located at http://errbot.io.

 

Errbot is a daemon process that can connect to Slack and converse with you and your team. Since Errbot can install dependencies automatically, we recommend you install it into a Python virtualenv (we’ll show you how). Once your local environment is ready, we will explore some cool features/hacks using the bot.

 

Some benefits of Errbot

Here are some of the benefits of using Errbot:

  • It’s managed by “chatops”: for example you can install and con‐ figure plugins just by chatting with the bot.
  • It’s chat system agnostic: write your plugin once, and run it on every supported chat system.
  • Its plugin repos are just Git repos.
  • It has a very gentle learning curve.
  • Its features include presence, mentions, cards, conversation flows, automation, and more.

 

Prerequisites

We recommend that you use the following to install Errbot:

  • Linux, Mac OS, or Windows
  • Python 3.4+
  • virtualenv

 

Windows and Python 2 compatibility

Err bot is compatible with Windows, but its support is limited (there is no daemon mode, so no background processing on Windows, and many public Errbot plugins are built to run on Linux). Err bot is compatible with Python 2.7.x, but this support is going away, and Python 3 will give you a better experience anyway with Python 3 specific features like type hints.

 

Installing Errbot

First you need to create a virtualenv. We recommend that you use virtualenv wrapper (see the note below for installation details) or the standard virtualenv tool explained as an alternative below.

 

Once you have virtualenvwrapper installed, it is quite easy to make a new virtualenv:

$ mkvirtualenv errbotve (errbotve) $

Installing virtualenvwrapper

virtualenvwrapper makes it really easy to create vir‐ tualenvs. See https://virtualenvwrapper.readthe http://docs.org/ for details.

 

As an alternative to virtualenvwrapper, you can use the standard vir‐ tualenv. With the commands below, we create a virtualenv in a hidden directory in your home directory called .errbotve:

$ sudo pip install virtualenv

$ virtualenv --python `which python3` $HOME/.errbotve

Already using interpreter /usr/bin/python3 Using base prefix '/usr'

New python executable in /home/gbin/.errbot-ve/bin/python3 Also creating executable in /home/gbin/.errbot-ve/bin/python Installing setuptools, pip, wheel...done.

$ source $HOME/.errbotve/bin/activate (errbotve) $

 

Once you have created a virtual environment, you can install Errbot on it. Pip, a tool used to install and manage Python software pack‐ ages, will automatically fetch all of the basic dependencies for the bot:

(errbotve) $ pip install errbot[slack] Collecting errbot

Downloading errbot-4.1.3.tar.gz (191kB)

100% | 194kB 2.2MB/s Collecting webtest (from errbot)


Downloading WebTest-2.0.21.tar.gz (66kB)

100% 71kB 6.5MB/s Requirement already satisfied (use --upgrade to upgrade): \

setuptools in ./.errbot-ve/lib/python3.5/site-packages (from errbot) Collecting bottle (from errbot)

Downloading bottle-0.12.9.tar.gz (69kB)

100% 71kB 6.3MB/s [...]

 

Successfully built errbot webtest bottle rocket-errbot yapsy markdown ansi pygments-markdown-lexer dnspython3 MarkupSafe cryptography cffi pycparser

 

Installing collected packages: six, WebOb, waitress, beautifulsoup4, [...]

Successfully installed MarkupSafe-0.23 Pygments-2.1.3 WebOb-1.6.1 [...]

$

 

Installing Slack support directly with Errbot

pip install errbot[slack] is a special syntax to make pip install the dependency required for Errbot to connect to Slack. Now, create a root directory for your Errbot instance; this is where it will store its data, configs, and logs. Errbot will also automatically create a subdirectory for your plugins in development:

(errbotve) $ mkdir ~/errbot (errbotve) $ cd ~/errbot (errbotve) $ errbot --init
Try Errbot locally in a console
Now that you’ve got Errbot installed, you’re ready to try it locally. To do so, run the following commands:
(errbotve) $ cd ~/errbot (errbotve) $ errbot
[...]
22:37:15 DEBUG errbot.errBot *** frm = gbin 22:37:15 DEBUG errbot.errBot *** username = gbin 22:37:15 DEBUG errbot.errBot *** text =
22:37:15 DEBUG errbot.errBot Triggering callback_message on VersionChecker 22:37:15 DEBUG errbot.errBot Triggering callback_message on Flows
22:37:15 DEBUG errbot.errBot Triggering callback_message on Help 22:37:15 DEBUG errbot.errBot Triggering callback_message on Plugins 22:37:15 DEBUG errbot.errBot Triggering callback_message on Backup 22:37:15 DEBUG errbot.errBot Triggering callback_message on Utils 22:37:15 DEBUG errbot.errBot Triggering callback_message on Health 22:37:15 DEBUG errbot.errBot Triggering callback_message on ACLS 22:37:15 DEBUG errbot.errBot Triggering callback_message on ChatRoom
>>>
Errbot presents you with a prompt >>>, and you can talk to it. For example, try the command !about:
>>> !about
[...]

 

This is Errbot version 4.1.3

  • Visit http://errbot.io/ for more information about errbot in general.
  • Visit http://errbot.io/en/latest/#user-guide for help with configuration, administration and plugin development.

Errbot is built through the hard work and dedication of everyone who contributes code, documentation and bug reports at

[...]

You can quit Errbot by pressing CTRL-C.

 

Using Errbot locally in a graphic mode (optional)

In addition to text mode (specified by the -T flag on the command line), Errbot also has a graphics mode (-G) that can display images, autocomplete commands, etc. This mode, like Text mode, is a development mode (available to all users that have access to the channel) that’s useful for iterating quickly while you’re developing plugins because Errbot will not connect to any external chat service. In order to use Errbot’s graphics mode, you need to install a dependency called PySide with pip install PySide

 

Using Errbot in local text mode while keeping your settings in http://fig.py You can always force back the Text or Graphics mode by starting Errbot with $ errbot -T or $ errbot -G to develop and test something quickly and locally without connecting to Slack.

 

Connect Errbot to Slack

In order to connect Errbot to Slack, first, you’ll need to create a bot user for your team using the form located at https:// http://TEAM.slack.com/services/new/bot where TEAM is the name of your Slack team

 

Copy and paste the API Token entry from the Integration Settings into the BOT_IDENTITY section of your con http://fig.py file, and set the backend to Slack. Also, you’ll need to set the BOT_ADMIN to a list of trusted users that can administer the bot.

 

Once you do all that, the content of http://config.py should look like something like this:

BACKEND = 'Slack' BOT_IDENTITY = {
'token': 'xoxb-56487685088-iaprBw0whgFhPIPL7Yr1E4Rs',
}
BOT_ADMINS = ('gbin',)

 

BACKEND value in http://config.py

Setting BACKEND to Slack in http://config.py makes Errbot try to connect to Slack. Other possible values are the other supported chat systems like hip chat, IRC, etc.

Now invite the bot user to your chatroom using the channel’s gear menu

 

Then select the newly created bot user

Now that Errbot is set up and invited to your Slack channel, you’re ready to start Errbot with no parameter:

(errbotve) $ cd ~/errbot (errbotve) $ errbot [...]

The bot user’s status-indicator cirlce should turn green in the chat-room you added Errbot to (see Figure ), and you should now be able to “talk” to it. You can try commands like !help, !whoami, and ! echo cock-a-doodle-do !.

 

Errbot’s status indicator turns green (note the shaded circle).

 

Going beyond

Errbot has a lot of features for plugin designers. We will explore some of them in the following hacks, but here are some pointers from the documentation:

  • Use persistence to store some data: http://errbot.io/en/latest/ user_guide/plugin_development/persistence.html
  • Make your plugin answer to events like presence: http:// http://errbot.io/en/latest/user_guide/plugin_development
  • Make your plugin answer to webhooks: http://errbot.io/en/latest/ user_guide/plugin_development/webhooks.html
  • Implement a conversational state or flow: http://errbot.io/en/ latest/user_guide/flow_development/index.html
  • Generate time-based events with scheduling: http://errbot.io/en/ latest/user_guide/plugin_development/scheduling.html

 

Now that you have Errbot up and running, it’s time to take advantage of what it can offer.

 

Organize tournaments with Errbot

In this simple hack, we’ll show you how to use the err- tourney plugin to organize tournaments and maintain a ranking system. The ranking system is based on the Elo system (a method for ranking players), and is suitable for any one-on-one game like chess, go, table tennis, air hockey, pool, etc.

 

To get started, we will show you how to find and install a plugin on Errbot. As of this writing, Errbot has more than 300 public plugins available online, so let’s examine how to search through the list.

 

Finding and installing err-tourney

If you setup Errbot with your Slack user as a BOT_ADMIN in the con http://fig.py file, you can administer the bot by sending it private messages.

 

For example, you can query the online plugin repository by sending the command !reports search tourney  Now that we have found the full name of the err-tourney plugin, we can install it simply by asking the bot to do so:

>>> !repos install errbotio/err-tourney [...]

 

Checking out what commands the plugin exposed

You can use !help, or if you know the name of the plugin, !help plus the name of the plugin to see the commands available:

>>> !help Tourney Tourney

 

Maintain an elo rating system.

!elim cancel - Cancel the current direct elimination tournament
!elim start - Start a direct elimination tournament amongst the players
!elo add - Add a player
!elo match - record a match result
!elo rankings - Printout the current elo rankings
!elo remove - Remove a player
!elo stats - Returns the current players statistics.

 

Start using err-tourney

Now you can start using err-tourney to add players:

>>> !elo add tali Player tali added

>>> !elo add stevo Player stevo added

>>> !elo add davy Player davy added

>>> !elo add pol Player pol added

>>> !elo add gbin Player gbin added

 

Once a result is in, you can start to record matches. Everyone starts with an elo ranking of 1500. After a match, the elo ranking of both involved players will swing depending on the results:

>>> !elo match pol davy pol

Game added pol won against davy. pol 1500 -> 1516

davy 1500 -> 1484

>>> !elo match tali davy tali Game added tali won against davy.

tali 1500 -> 1515

davy 1484 -> 1468

You have just learned seen how to install and use a plugin from a third party. Now let’s see how to create your own plugin.

 

Generate ASCII art with Errbot

In this hack, we will create from scratch a very simple plugin for Errbot that can generate ASCII art. To do this, we will use Errbot’s plugin-creation wizard.

 

First, go to your plugins directory (~/errbot/plugins), create a sub-folder for your plugin, and start the new-plugin wizard (the version prompts are left in this code snippet as default; you don’t need to do anything with them):

$ cd ~/errbot/plugins

$ mkdir err-big

$ cd err-big

$ errbot --new-plugin

This wizard will create a new plugin for you in '/home/gbin/projects/err-plugins/err-big'.

What should the name of your new plugin be?

> Big

What may I use as a short (one-line) description of your plugin?

> This plugin will make Errbot display text as large ASCII art. Which python version will your plugin work with? 2, 2+ or 3? I will default to 3 if you leave this blank.

Which minimum version of errbot will your plugin work with? Leave blank to support any version or input CURRENT to select the current version (4.1.3)

>  Which maximum version of errbot will your plugin work with? Leave blank to support any version or input CURRENT to select the current version (4.1.3)

>  Success! You'll find your new plugin at '/home/gbin/projects/err-plugins/err-big/big.plug'

(Don't forget to include a LICENSE file if you are going to publish your plugin)

 

This will create big.plug, a plugin descriptor, and http://big.py, a plugin template containing a simple example to get you started. To show you a basic case (just a command), you can trim down the more comprehensive plugin the wizard created to the following form:

from errbot import BotPlugin, botcmd
from pyfiglet import Figlet
class Big(BotPlugin):
"""This plugin will make Errbot display text as large ASCII art."""
@botcmd
def big(self, msg, args):
""" Generates a large ASCII art version of the text you enter."""
return "```\n" + Figlet(font='slant').renderText(args) + "\n```"
Next, install the required dependency pyfiglet:
$ pip install pyfiglet

 

Make Errbot automatically install plugin dependencies

You can also add a requirements.txt file in the root of your plugin containing the list of your dependencies (one per line) and Errbot will install them when you execute the command !repos install

 

Next, start Errbot in Text mode and it should respond to your new command:

$ errbot -T [...]

>>> !big Slack Hacks ! [...]

Now you can try it on Slack directly by restarting robot without the -T parameter. Then, type !big Some Text in your Slack channel and Errbot should respond with the ASCII version of the words you typed. You have learned how to write a plugin and test it locally on Slack. You can now develop and iterate on a plugin for Errbot.

 

Send Facepalm memes with Errbot

In this hack, we will create an Errbot plugin to display a random image (in our example, a meme) from the web to a Slack channel. The end result will look like the following

 

The base files to start with

This plugin will be structured like any other Errbot plugin. In the facepalms. plug file, enter the following:

[Core]
Name = Facepalms Module = http://facepalms.py
[Documentation]
Description = Display a random facepalm.
And in the base module http://facepalms.py, enter the following:
from errbot import BotPlugin, botcmd
class Facepalms(BotPlugin): pass

 

Gather some material for the plugin

You can find some funny facepalms on Google images Simply right-click/Ctrl-click on one and choose “View image” to copy its URL.

 

Slack image formats

Be sure to only select images that are in formats supported by Slack: GIF, JPEG, PNG, and BMP. Add your selection as a constant at the top of the http://facepalms.py module:

from errbot import BotPlugin, botcmd
IMAGE_URLS = [
'https://upload.wikimedia.org/wikipedia/commons' '/3/3b/Paris_Tuileries_Garden_Facepalm_statue.jpg', 'http://i.kinja-img.com/gawker-media/image/upload/' 't4pb8jdkc1c8bwcnck7i.jpg',
'https://qph.is.quoracdn.net/'
'main-thumb-t-475705-200-ygtxtfthoahjnxtxbndturgcxbnbyine.jpeg',
]
class Facepalms(BotPlugin): pass
Make Slack display an image
You can use the card feature (with send_card) to display the image as a Slack attachment:
import random
class Facepalms(BotPlugin): @botcmd
def fp(self, msg, _):
"""Displays a random facepalm."""
self.send_card(in_reply_to=msg, image=random.choice(IMAGE_URLS))

 

A word on identities in_reply_to is a shortcut that lets you avoid having to deal with public and private chatroom responses. You will have to use self.build_identifier(str) if you only have a textual representation of the room or the person you want to send the card to.

 

Now you can simply type !FP to trigger the plugin, which will display an image in response. This hack has shown you how to use Slack attachments to display images. But Slack attachments can do much much more, so please refer to the send_card documentation to explore the possibilities.

 

Make Errbot self-aware

This is a small trick you can play with Errbot. We’ve included it here mainly to show you some simple command parsing and how to build identifiers. The goal is to talk in a one-on-one channel with Errbot and use a command to make Errbot say something in a team channel.

 

The code structure of the http://say.py module for this trick is pretty simple:

from errbot import BotPlugin, botcmd
class Startle(BotPlugin): @botcmd(split_args_with=' ')
def say(self, _, args):
""" Make Errbot say something to a room or a person. ie. !say #General I think I am self-conscious. or
!say @gbin Hello !
"""
if len(args) < 2:
return 'The format is !say #room something.'
identifier = args[0]
try:
self.send(self.build_identifier(identifier), ' '.join(args[1:]))
except:
return 'Cannot find room or person %s' % identifier
return 'Message sent !'

 

split_args_with makes Errbot parse the input of the incoming message, instead of just sending it as a string in args. Errbot will send it directly as a list of strings cut by the character you gave as a parameter. Then, you can check whether args contains at least an identifier and a message.

 

We are assuming the first parameter is the identifier, so you need to convert this textual representation of the identifier into a real identifier with build_identifier. This will give you an object of type Identifier or Room that you can use in the send method.

 

No need to use self.send to reply

When they want to reply to a message from Errbot, a lot of Errbot plugin designers use … self.send(msg.frm, "my message") which is overly complex and even buggy in some cases.

 

Instead, you can simply use return "my message" and Errbot will reply to the command with the message returned as a string. This hack has shown you how easy it is to translate text identifiers like @gbin to identifiers Errbot expects, which allows you to send messages. Now let’s see how to can persist (store) objects on disk.

 

Create polls with Errbot

In this hack, we will create a plugin that serves as a virtual voting booth; in the process, we will explore how to use Errbot’s persistence feature. The goal of this hack it to be able to define a poll, such as “Which restaurant should we go to for lunch?”, let Errbot record all of the options, and finally tally the votes.

 

You can install a complete implementation of this plugin by sending the bot a private message: !repos install err-poll. We will walk you through all of it here so you can understand how to implement your own polls in Slack.

 

Data structure

First, we need to determine how to persist the data for the plugin. We need to persist a list of polls and their current options, plus current tallies. At the root of the persistence you have:

polls poll_name → PollEntry [dictionary]
current_poll poll_name [str]
Create a file called http://poll.py that will the Python module for this plugin. With PollEntry holding the options, the current counts and who has already voted, the code in http://poll.py looks like this:
from typing import List, Mapping
from errbot.backends.base import Identifier
class PollEntry(object):
"""
This is just a data object that can be pickled. """
def __init__(self):
self._options = {} self._has_voted = []
@property
def options(self) -> Mapping[str, int]:
return self._options
@property
def has_voted(self) -> List[Identifier]:
return self._has_voted

Storing objects with Errbot is very easy. The plugin itself, represented by self in Python, is a persistent dictionary. You can use the syntax self['key'] = value to store a value at a key.

 

Creating a poll

Now that you’ve created the plugin’s basic structure, let’s make them ! poll new command. (We will ignore all of the error conditions to simplify the implementation.) Here is http://poll.py with this !poll new creation command:

>class Poll(BotPlugin):
# activate is called every time the plugin is loaded.
def activate(self): super().activate()
# initial setup
# preallocate the 2 entry points to simplify the code below.
if 'current_poll' not in self: self['current_poll'] = None
if 'polls' not in self: self['polls'] = {}
@botcmd
def poll_new(self, _, title):
with self.mutable('polls') as polls: # see below.
polls[title] = PollEntry()
self['current_poll'] = title
return 'Poll %s created.' % title

 

Here we use with self.mutable('polls') as polls: because the entries on self are only persisted when they are written directly. The with construct is equivalent to:

polls = self['polls'] polls[title] = PollEntry()

self['polls'] = polls # This will persist 'polls'

So, now you should be able to test the command by running !poll new restaurants and it should create a new poll.

 

Adding an option

Now we need to add an option to the poll in http://poll.py:

@botcmd
def poll_option(self, _, option): current_poll = self['current_poll'] with self.mutable('polls') as polls:
poll = polls[current_poll] poll.options[option] = 0
return '%s:\n%s' % (current_poll, str(poll))
This code retrieves the current poll, adds an option with 0 votes, and displays the current state of the poll on chat.
Enabling voting
Let’s implement vote counting in http://poll.py with the !vote command:
@botcmd
def vote(self, msg, index): current_poll = self['current_poll'] index = int(index)
with self.mutable('polls') as polls: poll = polls[current_poll]
# msg.frm is of type Identity and they are guaranteed to be # comparable.
if msg.frm in poll.has_voted:
return 'You have already voted.'
# you can also persist Identity types
poll.has_voted.append(msg.frm)
# keys are in random orders, sorted helps to get a constant # one.
option = sorted(poll.options.keys())[index - 1]
poll.options[option] += 1
return '%s:\n%s' % (current_poll, str(poll))

 

This code gets the entry index for the user who is talking to the bot (msg.frm), and then check whether the user has already voted. If not, we record his/her vote, the fact that s/he has voted, and display the current state of the votes.

 

Let’s try recording some votes

Feel free to customize the code in this hack so you can craft your own polls in Slack.

 

Ambush a colleague with Errbot

The goal of this hack is to store a message that will be delivered to a Slack user as soon as she logs into Slack. The bot will contact her and send her a Slack attachment that includes your text and a red mark for dramatic effect.

 

Create the command

To get started, create a Python module in a new file called http://ambush.py. Next, create an !ambush command that simply records who the bot should contact and why. Here’s code for the command in http://ambush.py:

from errbot import botcmd, BotPlugin
class Ambush(BotPlugin): @botcmd(split_args_with=' ')
def ambush(self, msg, args):
if len(args) < 2:
return '!ambush @gbin emergency !! contact @growbot
as soon as possible' idstr = args[0]
if not self.build_identifier(idstr):
return ('Cannot build an identifier'
'on this chat system with %s' % idstr) self[idstr] = (msg.frm, ' '.join(args[1:]))
return 'Waiting for %s to show up...' % idstr

 

Assuming you’ve read the previous hacks (#35-#38), you should be familiar with build_identifier and persistence. Wait for a user’s presence status to change

 

Let’s add a special callback method that will be called every time a user changes his or her presence (online, offline, away, etc.). Here is the implementation of callback_presence in http://ambush.py:

from errbot.backends.base import Presence, ONLINE

# this is conventional and defined in BotPlugin

def callback_presence(self, presence: Presence):

# str(identifier) is the exact opposite of self.build_identifier

idstr = str(presence.identifier)


In action

# test if it is the presence we are interested in. # status gives a string representing the "online", # "offline", "away" status of the user.

if presence.status is ONLINE and idstr in self:

# retrieve back the message

frm, text = self[idstr]

# red for the dramatic effect. self.send_card(to=presence.identifier, body=text, color='red') del self[idstr] # it is done, cancel the alert.

 

This was a very simple example, but you can make a lot of variations on this hack depending on how your team and their Slack channels are organized.

Generate XKCD-style charts with Errbot

One of the main advantages of Errbot being written in Python is its huge ecosystem of bindings and libraries

 

In this hack, we will see how to use Matplotlib to generate fun charts like the one shown

This is fun, but this hack can be easily adapted for more serious uses like infrastructure monitoring, scientific applications, and more.

To install a complete version of this plugin, run the command! repos install err-xkcdcharts.

 

The base files to start with

The plugin we’re going to create in this hack will be structured like any other plugin. We will need some dependencies in requirements.txt:

matplotlib requests

requests is a popular Python library. It will be used to publish the image of the chart on the web so it can be displayed in Slack. mat plotlib is a popular scientific charting library.

 

You can create the xkcdcharts.plug and http://xkcdcharts.py files manually, or you can generate them with the command errbot --new- plugin. Let’s begin by manually creating an ini xkcdcharts.plug file with this content:
[Core]

Name = XKCDCharts Module = http://xkcdcharts.py

[Documentation]

Description = Draw XKCD looking charts in your chatroom.

[Errbot]

min = 4.2.0

 

Then create a base module in http://xkcdcharts.py with this content:

import io
from errbot import BotPlugin, arg_botcmd # Errbot base.
import requests # used for image upload
import matplotlib
matplotlib.use('Agg') # initializes matplotlib in "headless" mode import matplotlib.pyplot as plt # needs to be done after matplotlib.use plt.xkcd()
class Charts(BotPlugin): pass
After running pip install -r requirements.txt we are ready to implement the heart of the plugin.

 

The first thing we need to do is to generate a .png in memory and upload it to an image provider. In our code (shown below), we used http://uploads.im for the sake of simplicity, but you can also use Google Cloud Storage or Amazon S3 for that. Add the save_chart method to the Charts class http://xkcdcharts.py like this:

# .. in class Charts ...
def save_chart(self, plt):
with io.BytesIO() as img: plt.savefig(img, format='png') img.seek(0, 0)
req = requests.post('http://uploads.im/api',
files={'upload': ('chart.png', img)})
res = req.json()
url = res['data']['thumb_url'].replace('.im/t/', '.im/d/')
return url

This method simply takes a matplotlib plot, renders it in memory in a file-like object img, and then uses the API of http://uploads.im (http:// http://uploads.im/api) to store it on that site. The upload API call provides the final name of the file, and then we can construct a direct URL to the PNG, mimicking what the http://uploads.im frontend is doing.

 

As a parameter for our Errbot command, we will need to parse plenty of x,y pairs. Let’s get that out of the way with a small util function:

 def parse_tuple(t: str) -> (int, int): x, y = t.split(',')
return int(x), int(y)
Now, we are ready for a first simple charting helper for an xy chart. This helper takes a list of coordinates and graphs them on a 120x120 chart:
from typing import Sequence [...]
# in class Charts
def _xy(self, coords:Sequence[str]): fig = plt.figure()
ax = fig.add_subplot(1, 1, 1) ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') plt.xticks([]) # remove the ticks plt.yticks([])
ax.set_xlim([0, 120])
ax.set_ylim([0, 120])
xs, ys = zip(*(parse_tuple(coord) for coord in coords)) plt.plot(xs, ys, 'r')
return self.save_chart(plt)

Here is a minimal version of a charting command using this helper:

# still in class Charts
@arg_botcmd('coords',
metavar='coords', type=str, nargs='*',
help='coordinates to plot a line in x1,y1 x2,y2' 'separated by space')
def xy(self, _, coords=None):
return self._xy(coords=coords)

 

This will define a command !xy that takes a series of coordinates like 12,23 14,56 (with no space after the comma), so you can make arbitrary charts. Let’s try out our new command with a ramdom series

>>> !xy 0,10 30,40 50,30 70,70 90,50 100,100

 

Now we can add the ability to include notes and labels to our graphs by improving the _xy method as shown below:

def _xy(self, coords: Sequence[str]=None, note=None, xlabel=None, ylabel=None fig = plt.figure()
ax = fig.add_subplot(1, 1, 1) ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') plt.xticks([])
plt.yticks([])
if xlabel:
plt.xlabel(xlabel)
if ylabel:
plt.ylabel(ylabel) ax.set_xlim([0, 120])
ax.set_ylim([0, 120])
if coords:
xs, ys = zip(*(parse_tuple(coord) for coord in coords)) plt.plot(xs, ys, 'r')
if note:
note_msg, xy, xy_txt = note
plt.annotate(note_msg, xy=parse_tuple(xy), arrowprops=dict(arrowstyle
return self.save_chart(plt)```

 

We can now add a command to graph a funny, hardcoded, upward trend with a label:

@arg_botcmd('note', type=str, nargs=1, help='Why it is going down ?') @arg_botcmd('--xlabel', dest='xlabel', type=str, nargs=1, help='label for the @arg_botcmd('--ylabel', dest='ylabel', type=str, nargs=1, help='label for the def upchart(self, _, note=None, xlabel=None, ylabel=None):
"""
Just a canned case of xy with those parameters:
!upchart "your message" Message [--xlabel time] [--ylabel money]
!xy 0,100 70,100 100,0 --note [your message] 70,100 15,50
"""
return self._xy(coords=('0,10', '70,10', '100,100'),
note=(note[0], '70,10', '15,50'),
xlabel=xlabel[0] if xlabel else None, ylabel=ylabel[0] if ylabel else None)

Now, if you send the command to the bot on Slack !upchart "Started using Slack" --xlabel time --ylabel morale you should get our initial example, shown in.

 

This hack has a lot of moving pieces that you can reuse in your plugins. It was all pretty easy to do because we had libraries and friendly APIs for us to use. But what if you don’t have libraries and APIs? The next hack will show you how to scrape a website (extract information from it) in order to use it remotely via Errbot commands.

 

Test code snippets directly in a chat with Errbot

In this hack, we will see how to use websites that don’t have a public API. This is definitely not a recommended thing to do in normal circumstances since things might break unexpectedly, but it can be useful for bringing tools that would have been inaccessible to your Slack channel otherwise. The goal is to use the service at http://codepad.org through Slack and Errbot.

 

Some analysis of the posting page

Go to http://codepad.org and right-click and select “Show source” (for Google Chrome) or simply download the page with the command wget http://codepad.org. We are looking for what the page is posting to evaluate the user code entered on the page. First, we’ll need to locate the HTTP form in the page.

Here is an extract of the HTML source we are analyzing with some inline comments:

[...]
<div class="editor" id="editor">
<form action="" method="post" id="editor">
<table cellpadding="10" width="1%">
<tr>
<td colspan="2"> [...]
Then we locate in this same HTML source the various posted ele‐ ments, such as a “lang” field with plenty of options:
[...]
<tr>
<td style="vertical-align:top">
<span style="vertical-align:middle" class="label">Language:</span>
<br/>
<nobr>
<label>
<input style="vertical-align:middle" type="radio" name="lang" value="C" checked="checked"/>
<span style="vertical-align:middle" class="label">C</span>
</label>
</nobr>
<br/>
<nobr>
<label>
<input style="vertical-align:middle" type="radio" name="lang" value="C++"/>
<span style="vertical-align:middle" class="label">C++</span>
</label>
</nobr><br/>
<nobr>
<label>
<input style="vertical-align:middle" type="radio" name="lang" value="D"/>
<span style="vertical-align:middle" class="label">D</span>
</label>
</nobr>
<br/>
[...]
Still in this same HTML page, locate the code itself in code:
[...]
<td style="vertical-align:middle">
<textarea id="textarea" name="code" cols="80" rows="15" wrap="off">

</textarea>
</td>
[...]
Then a little bit further in the same page, note the private flag in
private:
[...]
<input style="vertical-align:middle" [...] type="checkbox" name="private" value="True" />
A little later in the same page, note the run flag in run:
[...]
<input style="vertical-align:middle" type="checkbox" name="run" value="True" checked="True" />
[...]
And finally, later in the page, the expected submit button posting in
Submit:
[...]
<input type="submit" name="submit" value="Submit"/> [...]

Now we have all of the elements needed to emulate the post from Errbot. Let’s code the posting part

 

Now we can create http://code.py, the main module of the plugin, like in the previous hacks, and start to implement a function to post to the site with the help of the requests library:

import requests
def scrape_codepad(code, lang='Python', private=True, run=True): data = {'code': code,
'lang': lang,
'private': str(private), 'run': str(run),
'submit': 'Submit'}
r = requests.post('http://codepad.org/', data=data)
if not r.ok:
return "Failed to contact codepad: %s" % r.content
return "SUCCESS:\n\n%s" % r.content
You should be able to test this function easily in an interactive Python interpreter by running the following commands:
$ python
>>> import code
>>> scrape_codepad('print(2+3)') SUCCESS:
[...]

 

Now we need to parse the resulting HTML.

Back to the drawing board

So first, let’s make up some Python test code to try out in code‐ http://pad.org:

for i in range(1, 11):

print("line %s" % i)

Once you have submitted the form, let’s try to find the result in the answer

 

Using the same technique as before, locate the interesting section of the HTML source we want to capture.

<a name="output">
<span class="heading">Output:</span> <-- anchor element to find first !-->
</a>
<div class="code"> <-- 1 -->
<table border="0" cellpadding="10" cellspacing="0">
<tr>
<td style="border-right:1px solid #ccc;text-align:right;vertical-align:top">
<div class="highlight">
<pre><a name="output-line-1">1</a>

<a name="output-line-2">2</a>

<a name="output-line-3">3</a>

<a name="output-line-4">4</a>

<a name="output-line-5">5</a>

<a name="output-line-6">6</a>

<a name="output-line-7">7</a>

<a name="output-line-8">8</a>

<a name="output-line-9">9</a>

<a name="output-line-10">10</a>

</pre>
</div>
</td>
<td width="100%" style="vertical-align:top">
<div class="highlight">
<pre>

line 1

line 2

line 3

line 4

line 5

line 6

line 7

line 8

line 9

line 10

</pre>
</div>
</td></tr></table>
</div>

We can see the 10 outputted lines and inline the heading span that we will anchor to in order to scrape the page.

We will use the library called Beautiful soup. This library has a very intuitive API to navigate through structures like this. More information about this library can be found at https://www.crummy.com/soft ware/BeautifulSoup/.

 

So, as mentioned earlier, we first find the “anchor element” commented on the source with the soup. find method. This anchoring element needs to be something that always appears with a specific marker, like the name output.

Next, we need to find the first div below (1). Then we can start to dive into the structure toward the first td of the table. You then find its sibling with findNext('td') and finally dive to the pre containing the response we are interested in capturing.

 

This logic looks like the following in the helper function

scrape_codepad:
from bs4 import BeautifulSoup
# [...]
def scrape_codepad(code, lang='Python', private=True, run=True):
# [...]
# at the end of scrape_codepad
soup = BeautifulSoup(r.content, 'lxml')
output = soup.find('a', attrs={'name': 'output'})
result = output.findNext('div').http://table.tr.td.findNext('td').div.pre.text
return result.strip('\n ')

 

Once a local test works with simple expressions like print(2+3), it is time to integrate with a real Errbot command. Doing so is trivial at that point:

from errbot import BotPlugin, botcmd
def scrape_codepad(code, lang='Python', private=True, run=True): [...]
class CodeBot(BotPlugin): @botcmd
def python(self, _, args):
""" Execute the python expression. ie. !python print(range(10))
"""
return scrape_codepad(args)

You can try your plugin locally with the command errbot -T:

$ errbot -T [,,,]

>>> !python print(3+2) 5

And finally on Slack with the command errbot.

This hack showed you how to extract a feature from an existing webpage and expose it as an Ernbot command on Slack. This con‐ cludes our series of hacks covering Errbot. Next, we’ll use another bot called Hubot.

 

Connect Hubot to Slack

Hubot is a hugely popular chatbot originally written by the folks at Github. A lot of already-made plugins exist for it. In this hack, we will show you how to set it up.

 

Prerequisites

You will need for this hack:

1. node.js: visit https://nodejs.org/ to download an installer for your system

2. update npm by executing the command sudo npm install npm

-g

 

Setup

Once you have node.js correctly installed, you can install two helpers (yo and generator-hubot) with this command:

$ npm install yo generator-hubot

 

Installing npm packages for the user only or system-wide.

By default npm installs the specified packages in the user’s directory, but you can use <code>npm -g</ code> for a system-wide installation (-g for global).

Now that you have the helpers to generate a Hubot, you need to create a working directory for your Hubot instance.

  • $ mkdir myhubot
  • $ cd myhubot
  • $ yo hubot

Hubot will ask you a series of questions, and you can answer slack for the adapter. You can remove the hubot-redis-brain entry from the file external-scripts.json. By doing so, you will be able to test Hubot without having to install a Redis database on your development machine.

You should be able to start and test Hubot locally by running the

hubot command:

$ bin/hubot [...]

myhubot>

 

Note that myhubot is the name of your Hubot instance, and this is also the prefix you use to send it commands, as shown in this example:

myhubot> myhubot help
Shell: myhubot adapter - Reply with the adapter
myhubot animate me <query> - The same thing as `image me`, except adds a few parameters to try to return an animated GIF instead.
myhubot echo <text> - Reply back with <text>
myhubot help - Displays all of the help commands that Hubot knows about. myhubot help <query> - Displays all help commands that match <query>. myhubot image me <query> - The Original. Queries Google Images for
<query> and returns a random top result.
myhubot map me <query> - Returns a map view of the area returned by
`query`.
myhubot mustache me <url|query> - Adds a mustache to the specified URL or query result.
myhubot ping - Reply with pong myhubot pug bomb N - get N pugs myhubot pug me - Receive a pug
myhubot the rules - Make sure hubot still knows the rules. myhubot time - Reply with current time
myhubot translate me <phrase> - Searches for a translation for the
<phrase> and then prints that bad boy out.
myhubot translate me from <source> into <target> <phrase> - Translates
<phrase> from <source> into <target>. Both <source> and <target> are optional

Feel free to try these preinstalled commands.

 

Connecting Hubot to Slack

To connect Hubot to Slack, we will use the preconfigured app on Slack. Go to the settings of your Slack channel and click on “Add an app or integration,” Search for the app called Hubot in the list, and install it for your team. This will start a small wizard where you will be able to choose a Slack username for your bot

 

In order to start and connect Hubot to Slack, you need to set an environment variable containing this Slack token, and ask Hubot to use the Slack adapter with the parameter -a:

$ HUBOT_SLACK_TOKEN=xoxb-48137346052-l3AKizOPnsunHka9qXFLtpHS \ bin/hubot -a slack

[Fri Jun 03 2016 16:25:45 GMT-0700 (PDT)] INFO Connecting... [Fri Jun 03 2016 16:25:46 GMT-0700 (PDT)] INFO Logged in as

myhubot of SlackHackBlog, but not yet connected [Fri Jun 03 2016 16:25:46 GMT-0700 (PDT)] INFO Slack client now

connected

[...]

Now invite Hubot into a channel from Slack

And invite Hubot into a team as well

Now that you have a Hubot instance installed, let’s see how to implement a plugin for it.

Create a “Hello World” plugin on Hubot

This hack will get you started developing plugins for Hubot.

 

Implementing your first plugin for Hubot

By default, the scripts directory, created during the Hubot installation, is automatically loaded when Hubot starts up, and you can define a plugin there. Try creating the file scripts/my.coffee with this content:

module.exports = (robot) -> robot.respond /hi/i, (res) ->

res.send 'Hello world!'

If you are not used to the Coffee script syntax, the project’s homepage has a good primer with a translated example in JavaScript: http://coffeescript.org/.

 

Another thing to keep in mind is the asynchronous nature of Node.js: it is programmed only by giving it functions (or callbacks). For example, the Hello World snippet above means, “export an anonymous function taking a robot parameter that will call .respond, taking a pattern and another anonymous function, and taking res as a parameter in order to call .send on it.” To know more about Node.js you can visit http://nodejs.org.

 

Testing your first plugin for Hubot

Once your plugin is defined, you can use it directly from the shell adapter, the command-line interface to Hubot:

$ bin/hubot myhubot>myhubot hi myhubot>hello world!

Now try the plugin on Slack by starting Hubot like you did at the end of Hack #42:

$ HUBOT_SLACK_TOKEN=xoxb-48137346052-l3AKizOPnsunHka9qXFLtpHS \ bin/hubot -a slack

Once the bot is started, you’ll be able to interact with it on Slack

Now that Hubot is installed and you know how to create plugins for it, you can implement more complex interactions with Hubot.

 

Connect Lita to Slack

Lita is a bot written by Jimmy Cuadra that is extensible in Ruby. Since a Ruby environment can be tricky to install and because Redis (a database) is a requirement for Lita, it is recommended that you use a virtual machine to set up the development environment that will install all the dependencies correctly for you.

In this hack, we will show you how to set up a development environment using Virtualbox, which you will control from the command line with Vagrant (a command-line tool for Virtualbox).

 

Dependencies

First, you need Virtualbox from Oracle. You can download it and follow the installation instructions from https://www.virtualbox.org/. Then you will need Vagrant. You can follow the installation instructions at https://www.vagrantup.com/. Once you have Virtualbox and Vagrant installed, you’re ready to install Lita.

 

Boot up your Lita virtual machine

Clone the Vagrant descriptor files with git clone:

$ git clone https://github.com/litaio/development-environment.git \

lita-dev

 

Then start the virtual machine with vagrant up:

$ cd lita-dev

$ vagrant up

Linux dependency on nfsd

On Linux, Vagrant will ask to have a running nfs daemon to be able to boot. For example, on Arch Linux, you’ll need to install the nfs-utils package and run systemctl start nfs-server.service, checking the details for your specific distribution if you are missing the package.

 

From here you should be able to SSH in your new environment with

vagrant ssh:
$ vagrant ssh
CoreOS stable (1010.5.0) core@lita-dev ~ $
Then you can enter the Lita development environment with lita- dev:
core@lita-dev ~ $ lita-dev lita@5fac349fcb46:~/workspace$
Next, initialize a new Lita project with lita new. Doing so will cre‐ ate a base configuration for your bot:
lita@5fac349fcb46:~/workspace$ lita new create lita
create lita/Gemfile
create lita/lita_config.rb lita@5fac349fcb46:~/workspace$
Finish the installation by requesting to install all of the dependencies with bundle:
$ cd lita
$ bundle
You can now talk to the bot by using the lita command:
$ lita
Lita > Lita, help Lita, help
Lita: help - Lists help information for terms and command the robot will respond to.
Lita: help COMMAND - Lists help information for terms or commands that begin with COMMAND.
Lita: info - Replies with the current version of Lita.
Lita: users find SEARCH_TERM - Find a Lita user by ID, name, or mention name.

Now that you have Lita up and running, you’re ready to connect it to Slack.

 

Connect Lita to Slack

In the Slack channel of your choice, click the Settings menu (the gear icon), choose “Add an app or integration,” select “Brilliant bots,” and then choose Lita. From there, the documentation in the wizard will help you with changing the adapter and adding your slack token.

 

Once you’ve followed those instructions, you can come back to your Lita development environment and prepare it so it can connect to Slack.

First you need to rerun bundle so the new dependency on lita- slack is taken into account.

Finally, fire up lita and it should connect to Slack:

lita@4ac79d5e8890:~/workspace/lita$ lita

fatal: Not a git repository (or any parent up to mount point

/home/lita/workspace)

Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set). [2016-06-03 18:32:51 UTC] INFO: Connected to Slack.

 

Then invite Lita to your channel

Now that you have Lita installed, let’s create a handler, a type of plugin that adds a chat/command feature.

Create a “Hello World” handler on Lita

Lita has 3 types of plugins:

  • adapters are pieces of glue code that connect Lita to a specific type of chat system (there is one for Slack, of course, but also for IRC, Hipchat, etc.)
  • handlers add a chat/command feature.
  • extensions are advanced plugins that expose transverse features that hook into Lita callbacks (i.e., extending Lita itself).

This hack will show you a minimal version of a handler.

 

Implementing your first handler

To begin, start a separate shell from the host machine in the Git repo you originally cloned from the Lita installation. Note how the

~/workspace from the Lita docker container is mounted in the workspace directory in the Git repo. This allows you to use your favorite editor to develop on Lita.

 

You can come back to your workspace directory with cd ~/workspace on the development environment. We are going to ask Lita to scaffold the new handler we are going to develop. By “scaffold,” we mean to create the various files you’ll need to make a minimal handler. This is done via the lita handler command:

$ lita handler slack hacks [...]

(Lita will ask you a few Jenkins CI and coverage questions. Reply no to each of them.)

 

This command will create a lita-slack hacks Ruby gem that will hold your handler. It comes with to-dos in the gemspec you will need to fix before being able to build the gem. So the next step is to edit the lita-hello.gemspec file and set reasonable values like the ones shown here:

[...]

spec.name = "lita-Blackhawks"

spec.version = "0.1.0" spec.authors = ["Guillaume Binet"]

spec.email = ["gbin@generic.net"] spec.description = "This is to say hello" spec. summary = "This is a summary" spec.homepage = "http://lita.io" spec.license = "GPL"

spec.metadata = { "lita_plugin_type" => "handler" } [...]

Now that the gem is specified, it should build and install correctly from the ~/workspace/lita-hello directory in your dev shell:

$ bundle [...]
Using lita-slack hacks 0.1.0 from source at `.`
Bundle complete! 6 Gemfile dependencies, 30 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.

 

You’ll need to make the Lita Gem load your handler gem, (rather than look for it on the Web), which you can do by forcing it to load from the disk in the ~/workspace/lita/Gemfile file:

source "https://rubygems.org" gem "lita"

gem "lita-slackhacks", path: "../lita-slackhacks"

 

This handler doesn’t actually do anything yet, so it’s time to add a command to your plugin. The scaffolding generated a class called SlackHacks that inherits from Handler in lib/lita/handlers/ slackhacks.rb. You can see that it auto registers at load time with Lita:

Lita.register_handler(self)

You’ll need to add two things to this class to make the handler respond to a command. One is a simple method that gets a response object as a parameter. The other is registration for this method as a command for Lita.

 

Here is the full file with a simple Hello World response implemented.

module Lita module Handlers
class Slackhacks < Handler
route(/^hi\s+(?<name>.+)/, :hi)
def hi(response)
name = response.match_data['name']
return response.reply('hello ' + name + ' !')
end
Lita.register_handler(self)
end end
end

 

Testing our first handler on Lita

To test out your new handler, first you need to restart Lita. You can do that from your workspace by doing the following:

lita@4ac79d5e8890:~/workspace/lita$ lita
fatal: Not a git repository (or any parent up to mount point
/home/lita/workspace)
Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set). Type "exit" or "quit" to end the session.
Lita > hi world hi world
hello world !
Lita > lita, hi world lita, hi world
hello world ! Lita >
You’ll notice that Lita will answer either a normal message or when it has been summoned. You can restrict its responses to the latter by adding a command: true during the registration of the command.
route(/^hi\s+(?<name>.+)/, :hi, command: true)
Lita > hi toto hi toto
Lita > lita, hi toto lita, hi toto
hello toto ! Lita >

 

When you try it out on Slack, it should also respond to your little handler now From here, you can start implementing your own handlers pretty easily. Now let’s see how to create a bot in Java.

 

Prep the Simple Slack API to build bots

The Simple Slack API is written in Java and is freely available on Github https://github.com/Ullink/simple- slack-API. It allows Java developers, or other Java VM language developers, to quickly build a fully functional Slack bot that runs as a standalone application. As you’ll see, it can also be used to quickly develop some hacks.

 

Simple Slack API internals

Here’s how all Slack bots made with the Simple Slack API work:

1. The bot establishes a WebSocket connection using the Slack RTM API.

2. Once it’s connected, the bot receives various information about the team such as users, channels, etc.

3. Once all of this information has been received, the library keeps a web socket thread running that unmarshalls (deserializes) the Slack events into Java objects. When an event arrives, the library dispatches them to all registered listeners.

4. A heartbeat function is included in the library and is set to automatically monitor the connection status by sending Slack ping requests at regular intervals.

 

Here’s an example of a JSON event sent over the WebSocket connection:

{
"type": "message",
"user": "U042PASSS",
"text": "Hello world",
"bot_id": "B023PAXME",
"user_team": "T055KFDH",
"team": "T055KFDH",
"user_profile": {
"avatar_hash": "03f50aa1ce70",
"image_72": "https:\/\/avatars.slack-edge.com\/2015-03-18\/4aze.jpg",
"first_name": "Rob", "real_name": "Rob Ot", "name": "rob_ot"
},
"channel": "D023PFEF",
"ts": "1465298109.000035"
}

 

A bot built from Simple Slack API can react to these events using a set of listeners, and by using the Slack Web API RPC methods to act on the channels: sending a message, creating a channel, pinning a reaction emoji, etc.

 

Simple to use

By handling the WebSocket connection, the JSON stream, and API mappings, the Simple Slack API lets you focus on the business logic you want to implement on top of Slack. You shouldn’t have to bother about the connection status, or parsing JSON (unless you plan to code some tricky bots), or mastering the HTTP protocol (knowing about this is helpful but not mandatory). The core class of the API is SlackSession, which lets you register listeners or send commands just by calling methods.

 

Initiating a Slack session

The first thing you need to do is to instruct Simple Slack API to connect to Slack by opening a session. Once opened, this session will be the entry point that you can use to interact programmatically with the Slack team. Here’s how to open a session:

SlackSession session = SlackSessionFactory.

createWebSocketSlackSession("bot-token-here");

session.connect();

 

That’s it. With these two lines of code, your bot is connected to a Slack team.

Accessing the team’s users and channels

Once the session is opened, you can access the team user directory or browse the public channel list from the newly created session.

Here’s how to retrieve a channel:

 searching by channel name
SlackChannel channel1 = session.findChannelByName("cool-channel");
// searching by channel id
SlackChannel channel2 = session.findChannelById("C0123456");
// Fetching all the channel list
Collection<SlackChannel> channels = session.getChannels();
And here’s how to retrieve a user:
// searching by user name
SlackUser user1 session.findUserByUserName("john.doe");
// searching by email
SlackUser user2 session.findUserByEmail("john.doe@acme.com");
// searching by user id
SlackUser user3 session.findUserById("U012345");
// Fetching all the user list
Collection<SlackUser> users = getUsers();
// Knowing the user the bot is using to connect to Slack
SlackPersona botId = session.sessionPersona();

 

Ids

In the code used in this hack, you can see some methods for retrieving users or channels if you know their ID. The ID is a unique internal identifier created and managed by Slack.

 

Interacting with the team

Now that you have an open session and you know how to get the team’s channels and users, you can interact with those objects. All of this method use the Slack Web API described here https:// api.slack.com/methods. Virtually all of the Slack Web API methods are accessible through this library.

 

Here’s how to join a channel:

session.join channel("slack-hacks");
And how to post a message on a channel:
SlackChannel channel = session.findChannelById("C0123456"); session.sendMessage(channel, "Hello world");
And how to invite a user to a channel:
SlackChannel channel = session.findChannelById("C0123456"); SlackUser user = session.findUserByUserName("john.doe"); session.inviteToChannel(channel, user);

 

A bot connected to a Slack client

To get a better understanding of Simple Slack API, think of the Slack bot you are developing as being connected like a standard user using a Slack client. The commands you are sending would behave in the same way if you were using the client feature. For example, joining a channel that doesn’t exist creates it, yet posting a message on a channel you have not joined will fail.

 

Listening to team events

You can make the bot aware of what is going on in the channel. There are dozens of different events to listen to, so let’s examine the most interesting ones and see how to easily react to these events.

 

Here’s how to make your bot listen to a posted message:

session.addMessagePostedListener(new SlackMessagePostedListener() { @Override
public void onEvent(SlackMessagePosted event, SlackSession session) {
// put code here that you want to execute on such event
}
});
And here’s how you make the bot listen with a lambda expression:
session.addMessagePostedListener((_event, _session) -> {
// put the code here that you want to execute on such event
});

 

Your listener will get all of the message events as its first parameter regardless of which channel the message was posted on or which user posted it (including the listening bot itself). So, generally, the first thing the listener code will do is to filter out events based on the attributes of channels/users. Those attributes are available on the event object itself.

 

Listening to channel creation

The example below combines all the things you’ve seen so far to interact with the team and the channel-creation event listening mechanism. This code shows how to have your bot join each newly created channel (except those it has created itself):

session.addchannelCreatedListener((_event, _session) -> {
if (_event.getCreator().getId().equals(
_session.sessionPersona().getId())) {
_session.joinChannel(_event.getSlackChannel().getName());
}
});

 

Now that you have been introduced to the Simple Slack API, let’s put it to use in the next hack.

 

Build a JSON pretty-printer bot

In this hack, you’ll learn how to build your first Slack bot using the Simple Slack API library. This bot will prettify (reformat nicely) any JSON file that is sent to it. This is a convenient tool to have when you are developing‐ ing tools around the JSON based protocol, like the Slack RTM API.

 

In this step-by-step example, we will assume you know how to code in Java and that you have JDK 8 already installed. We will use Gradle (http://gradle.org/) as our build tool to put everything together.

Setup

 

Before you can start building your bot, you need to do some setup:

1. Download the latest distribution of Gradle from http:// gradle.org/gradle-download/

2. Unzip the distribution into a directory of your choice

3. Create a GRADLE_HOME environment variable

4. Add GRADLE_HOME/bin to your PATH environment variable

 

After you’ve performed those setup steps, you should be able to run Gradle from the command line. Now that you have Gradle set up, you’re ready to perform the remaining setup steps:

1. Create a directory where you want to put your bot source files

2. Create a build. gradle file with the following content:

apply plugin: 'java' repositories {

mavenCentral()

}

dependencies {

compile 'com.ullink.slack:simpleslackapi:0.6.0' compile 'org.apache.httpcomponents:httpclient:4.4' compile 'com.cedarsoftware:json-io:4.4.0'

}
  • 1. Create the following directory: class='lazy' data-src/main/java/com/oreilly/ slackhacks
  • 2. In this newly created directory, create the following file: JSON PrettyPrinter.java

+ build.gradle

+ If you are not familiar with Gradle, build.gradle is used to describe the project’s structure, its dependencies, and how to retrieve them. So in this hack, you will use the last version of simple slack API and json-io, an open source library (https:// github.com/jdereg/json-io), which provide a simple tool to prettify JSON. You are now ready to write the bot code. To keep things as simple as possible, everything in his hack will be written in the main method in a single class.

 

Bot skeleton

We have a clear vision of what the bot is supposed to do (prettify JSON files), so let’s try to define a skeleton. As you’ve seen, it’s quite easy to create a Slack session, add a listener, and connect to a Slack team. So, let’s create a skeleton for the bot:

public class JSONPrettyPrinter {
private static final String TOKEN = "insert_here_your_bot_token";
public static void main(String [] args) {
//create a session using the token
SlackSession session = SlackSessionFactory.createWebSocketSlackSession(TOKEN);
//add to the session a listener to get informed of all
//message posted that the bot could be aware of
session.addMessagePostedListener(JSONPrettyPrinter::processEvent);
//connect to the Slack team
session.connect();
//make the main thread wait and delegate the event processing
//to the session, dispatching them to the listener
Thread.sleep(Long.MAX_VALUE);
}
}

 

So far, there’s nothing new here except the call to the processEvent static method. Let’s have a closer look at what this method should do.

 

Processing the event

When the bot receives a SlackMessagePosted event, the logic that handles the event is easy to implement:

Does the event need to be processed? If not, then don’t process it

If the event needs to be processed:

  • — download the file attached to the message
  • — format the JSON
  • — send back a new file containing the formatted JSON

If an issue occurs during the processing (for example, the bot is unable to download the file, or the file doesn’t contain JSON formatted text) then notify the user of this fact.

 

In Java, this is expressed as follows:

private static void processEvent(SlackMessagePosted event,
SlackSession session) {
// does the event needs to be processed?
if (!isFileSentFromAnotherUser(event, session)) {
return;
}
//trying to pretty print the file
try {
sendBackPrettifiedFile(event, session, formatJson(downloadFile(event)));
} catch (Exception e) { failOnException(event, session, e);
}
}

 

Determining whether the event needs to be processed

The isFileSentFromAnotherUser method serves as a filter that dis‐ cards every event the bot doesn’t have to process. Here are the events the bot should ignore:

  • messages without a file attached to them
  • messages sent on a non-direct channel
  • messages sent by the bot itself

Here is the implementation of this using Java:

private static boolean isFileSentFromAnotherUser(SlackMessagePosted event,
SlackSession session) {
//if the event is not on a direct channel
if (!event.getChannel().isDirect())
return false;
//if the event was triggered by the bot
if (event.getSender().getId().equals( session.sessionPersona().getId()))
return false;
//if the event doesn't contain a file
if (event.getSlackFile() == null) return false;
//otherwise
return true;
}

 

Download the file attached to the download file

Whenever a file is referenced in a Slack event, the URL to download it is provided. For security reasons, this URL is not accessible to non-authorized users. In order to download a private Slack file using an HTTP GET request, a bot has to provide its token key in a header of the GET request using this format: Authorization: Bearer xxxxxx, where xxxxxx is the bot’s token.

 

In this hack, we will use the Apache HttpClient library (https:// hc.apache.org/httpcomponents-client-ga/index.html) to perform the HTTP GET call with the correct header. Let’s see how to download a Slack file in Java:

private static String downloadFile(SlackMessagePosted event)
throws IOException {
//creating a simple http client
HttpClient client = HttpClientBuilder.create().build();
// defining a get HTTP request
HttpGet get = new HttpGet(event.getSlackFile().getUrlPrivate());
// adding the Authorization header with the bot token
get.setHeader("Authorization", "Bearer " + TOKEN);
// send the get request
HttpResponse response = client.execute(get);
// initiate a reader to read the response
BufferedReader buffer = new BufferedReader(
new InputStreamReader(response.getEntity().getContent()));
// collect all the file lines and return the content
return buffer.lines().collect(Collectors.joining("\n"));
}
Format the JSON
As mentioned at the beginning of this hack, we’re going to use JSON-IO to format the JSON data, thanks to its JsonWriter class, which is straightforward:
private static String formatJson(String jsonString) {
return JsonWriter.formatJson(jsonString);
}

 

Send back a new, formatted JSON file

Here we will use the sendFile method provided by the SlackSession class, giving the name of the file and using the prettified JSON string as data:

private static void sendBackPrettifiedFile(SlackMessagePosted event,
SlackSession session, String formattedJson) {
//sending a file to the same direct channel, using the String byte
//array and a new filename: the original filename prefixed with pretty_ as
session.sendFile(event.getChannel(),formattedJson.getBytes(), "pretty_"+event.getSlackFile().getName());
}

 

Handling processor errors

If the file can’t be downloaded or it isn’t a JSON file, an exception will be thrown, and the bot has to notify the user it wasn’t able to process the file. Sending a message is the simplest way to do that:

private static void failOnException(SlackMessagePosted event,
SlackSession session, Exception e) {
//we're responding on the direct channel uing a simple message
// that an issue occured during the file processing
session.sendMessage(event.getChannel(),
"Sorry I was unable to process your file : " + e.getMessage());
}

 

And you’re done. Putting it all together will take less than 100 lines of Java code. You just have to build a JAR with all of the dependencies with the standard tool (https://docs.oracle.com/javase/tutorial/ deployment/jar/build.html) or your favorite IDE.

 

Let’s look at how this bot behaves. We took the JSON extract we presented during the introduction of this library and removed all of the formattings.  This hack showed you how to build a bot to prettify any JSON posted to it. Let’s now see a hack that dispatches customer support requests through Slack.

 

Dispatch customer support through Slack with Smooch

Thanks to its channel features, Slack is a good way to centralize the communication within your organization. But you can’t invite all of your customers to your Slack team. In most cases, customer support is done through another medium (email, forum, instant messaging).

 

Smooch provides a service that lets you link your customer support tool to a Slack team channel. This is a nice integration, but the channel can be very busy and your customer service department can be quickly mix up messages trying to answer them. A solution to this issue is to add to this Smooch integration a Slack bot that dispatches requests. In this hack, we’ll show you how to integrate Smooch with Slack and add the dispatch bot.

 

Installing Smooch

You first have to create an account on the smooch website: https:// smooch.io/.

 

Here are the steps you’ll need to follow during this creation:

  • 1. Once your email is confirmed, you will need to configure your smooch account by answering a few questions asked by a robot.
  • 2. Select the way your customers are reaching your customer support.
  • 3. Select the solution the customer support team will use to receive customer requests.

 

In this hack, emails are used as the method for reaching customer support. Smooch will create an email address that forwards the messages sent to it to your Slack channel. Alternatively, you can use your own support email by enabling the email forwarding in the smooch configuration screen.

 

By default, on Slack, the messages are sent to the #general channel, but you can choose a different channel in Smooch’s Slack settings

Standard Smooch use case

Now that Smooch is set up, let’s see what happens when you receive a customer support request. Imagine a customer sends an email to customer support:

to: help@acme.com Subject: need help Content :

hey, I need help

On Slack, the customer support team receives a message notifying them that the customer sent an email and that a dedicated channel has been created to deal with it 

 

A customer support representative can then answer the customer’s request using the /sk command:

/sk Hello Sir, could you please elaborate? Best regards, John Smith

And the customer instantly receives the following email:

from: help@acme.com Subject : Re: need help Content :

Hello Sir, could you please elaborate? Best regards, John Smith

 

Leveraging Slack’s history feature

The Smooch Slack integration will create a channel for each new user and will auto archive the channel after a specific duration of inactivity (6 hours by default). If the same user sends a new request a week later, the archived channel will be unarchived and all the history from past requests will be kept.

 

Creating the customer support dispatcher

Now that you have Smooch up and running, we’re going to create a Slack bot dispatcher in order to distribute the incoming customer support requests. The dispatcher will report who should take care of the incoming customer support requests and send a message to the customer support team member notifying him of the request he or she has to handle. This reduces the risk of having a question go unanswered or having more than one customer service rep responded.

 

The Slack bot will be developed in the Java language using the Simple Slack API library. We won’t detail the Java environment setup procedure since everything will fit into a single class.

Slack bot skeleton
Here is the bot’s skeleton:
private static final String TOKEN = "insert your token here";
public static void main(String [] args) throws Exception {
//creating the session
SlackSession session = SlackSessionFactory.createWebSocketSlackSession(TOKEN);
//adding a message listener to the session
session.addMessagePostedListener(
CustomerSupportDispatcher::processMessagePostedEvent);
//connecting the session to the Slack team
session.connect();
//delegating all the event management to the session
Thread.sleep(Long.MAX_VALUE);
}

As in the previous hack, we reproduce the same main structure as the JSON pretty printer, and all of the interesting code is in the mes‐ sage processor: the processMessagePostedEvent method.

 

Message processor structure

Here’s how the message processor is structured:

private static void processMessagePostedEvent(SlackMessagePosted event,
SlackSession session) {
//filtering all the messages the bot doesn't care about
if (isNotASmoochMessage(event)) {
return;
}
//notifying the CS member
notifyCustomerSupportMember(event,
selectCustomerSupportMember(event,session),
session);
}

The processor is divided into three steps:

  • 1. Filter out the messages not coming from Smooch
  • 2. Select a customer support representative
  • 3. Notify the rep that he has an incoming request to process

 

Message filtering

In this method, some validation on the event is done to discard events we don’t want to process; verifying the sender’s name and message type should be sufficient:

private static boolean isNotASmoochMessage(SlackMessagePosted event) {
//if the event is not a bot message
if (event.getMessageSubType() != BOT_MESSAGE) {
return true;
}
//if the sender is not named Smooch
if (!"Smooch".equals(event.getSender().getUserName())) {
return true;
}
return false;
}

 

Selecting a customer support rep

For this step we’ve chosen to keep things simple, so the selection will be made randomly among all of the users in the support channel that are not bots and are currently active:

private static SlackUser \ selectCustomerSupportMember(SlackMessagePosted event,
SlackSession session) { SlackChannel channel = event.getChannel(); Collection<SlackUser> users = channel.getMembers(); List<SlackUser> selectableUsers = users.stream()
.filter(user -> !user.isBot() && \ session.getPresence(user) == ACTIVE)
.collect(Collectors.toList());
return selectableUsers.get((int)Math.random()*selectableUsers.size());
}

 

Notifying the rep of an incoming request

In this step, we first get the channel reference that will be used to send messages. Smooch writes this reference in the attachment it posts, so we need to parse the underlying event JSON to get the attachment details. (We won’t go over the details of the JSON parsing—you can check it out in this hack’s source code on GitHub.)

 

After this parsing, the Slack bot will send a message to the support channel to notify the other reps that a team member has been selected to handle the issue. It also sends a direct message to the selected team member to inform him/her that he/she has to take care of this request. This code shows the flow that is implemented from the notification to the customer support member:

private static void notifyCustomerSupportMember(SlackMessagePosted event,

SlackUser nominee, SlackSession session) {

String channelReference = getCreatedChannelReference(event); session.sendMessage(event.getChannel(), "<@" + nominee.getId() + ">:" +

" will handle the issue in " + channelReference); session.sendMessageToUser(nominee,

" Could you please handle the issue in " + channelReference,null);

}

This hack showed you how to implement a more complex interaction between your Slack bot and your users. This is a good base to build upon; for example, by adding a persistence layer, you can extend this bot to store interesting statistics like how many requests a support assignee has been asked to handle or the most common keywords in those customer requests.

Now let’s see how to make a training bot.

 

Create a bot to check your multiplication skills

You would probably like to train your brain, so why not have a bot to ask you the results on multiplication tables, measuring the total time you need to give the correct answers on ten multiplication sets? We will produce such a Slack bot in this hack. We’ll do this using the Simple Slack API framework and Java.

 

Slack bot skeleton

This hack uses the same bot skeleton you saw in hack #48. We’re going to create a session, register a message post listener, and connect the session to the Slack server.

Message processor structure Here is the event handler:
private static void processMessagePostedEvent(SlackMessagePosted event,
SlackSession session) {
handleTestTrigger(event, session); handleAnswer(event, session);
}

The processor has to handle two kinds of messages:

  • detecting a keyword to launch the test
  • detecting an answer to a running test

The following sections explain how to do so. Handling test launch requests

 

To launch a test, users have to send a !times command to the bot through a direct message. The bot will then check whether the user is currently running a test, and if not, will store the test variables in a GameSession instance (keeping track of time spent to answer, and some other values). Then it starts the test. Here’s the code that performs all of these steps:

>private static void handleTestTrigger(SlackMessagePosted event,
SlackSession session) {
//trigger is accepted only on direct message to the bot
if (!event.getChannel().isDirect()) {
return;
}
//looking for !times command
if ("!times".equals(event.getMessageContent().trim())) {
// check whether a game is already running with this user,
// and if so, ignore this command
if (gameSessions.containsKey(event.getSender().getId())) {
return;
}
GameSession gameSession = prepareGameSession(event, session); gameSession.timer.start();
sendTimes(gameSession, session);
}
}

 

Handling test answers

Answers are numbers only and are sent on the direct channel between the user and the Slack bot. If the user hasn’t started a test and sends the bot a numeric message, the bot should ignore that message. If a number is given during a test, then the bot will react according to whether the answer is true or false.

Here how these guidelines translate into code:

private static void handleAnswer(SlackMessagePosted event,
SlackSession session) {
//no test launched for this user
GameSession gameSession = gameSessions.get(event.getSender().getId());
if (gameSession == null) {
return;
}
//an answer should be given on a direct channel
if (!event.getChannel().isDirect()) {
return;
}
//an answer is a number
String answerValue = event.getMessageContent().trim();
try {
int resultGiven = Integer.parseInt(answerValue);
if (resultGiven == gameSession.goodResult) {
//correct answer
goodAnswer(event, session, gameSession);
} else {
wrongAnswer(event, session);
}
} catch (NumberFormatException e) {
//ignore the result
return;
}
}

 

Sending a test question

When the test starts or when a user gives a correct answer, the bot randomly selects two numbers between 1 and 10 and sends the multiplication problem to the user as a message:

>private static void sendTimes(GameSession gameSession,
SlackSession session) {
//select two values to multiply
int a = 1 + (int) (Math.random() * 10); int b = 1 + (int) (Math.random() * 10); gameSession.goodResult = a * b;
gameSession.questionTimestamp = session.sendMessageToUser(
gameSession.user, a + " x " + b, null).getReply().getTimestamp();
}

 

Correct answer behavior

The following code tells the bot to indicate a correct answer by pinning a checkmark emoji on it. The bot then computes the time spent to answer and moves to the next test step (either another multiplication problem or the end of the test):

private static void goodAnswer(SlackMessagePosted event, SlackSession session,

GameSession gameSession) { session.addReactionToMessage(event.getChannel(), event.getTimeStamp(),

"white_check_mark"); computeTime(event, gameSession); nextTestStep(session, gameSession);

}

 

Wrong answer behavior

As with a correct answer, the bot pins an emoji to the user’s response, but this time a red X is used to show the user the answer was wrong:

private static void wrongAnswer(SlackMessagePosted event,

SlackSession session) { session.addReactionToMessage(event.getChannel(), event.getTimeStamp(), "x");

}

 

Ending the test

At the end of the test, the Slack bot displays the total time spent to give the ten answers and removes the game session to allow the user to start a new test.

private static void showTestResults(GameSession gameSession,

SlackSession session) { session.sendMessageToUser(gameSession.user, "You took " +

gameSession.cumulativeTime + " seconds to complete the test", null); gameSessions.remove(gameSession.user.getId());

}

In this hack, we saw how to interact with users and maintain a state machine to keep track of the progress of a game. Now, let’s next see how we can use the Simple Slack API to run a “quiz” on a channel.

 

Create a bot to run a quiz on a channel

Your team will likely have a Slack channel dedicated to entertainment, fun or challenges. Why not set up a bot to ask the questions? It could be an impartial referee and it could enforce a time constraint on the responses. This hack—which will be developed in Java—shows you a simple way to develop such a Slack bot using the Simple Slack API framework. The bot will send questions and propose four possibles answers.

 

Any user on the channel can give one answer to each question. The first one to give the right answer is awarded a point. If the right answer is not given within 10 seconds, the bot asks a new question. By the end of the question session, the bot displays a scoreboard for all of the users who have answered at least one question.

 

Slack bot skeleton

This bot uses the same pattern as the previous bot, using a similar framework. There’s one additional step in this hack—loading the quiz questions—but that is not really related to Slack. It is just a matter of retrieving question definitions from a JSON file (see this blog’s Github repository for an example).

 

The main entry point looks like this:

 public static void main(String[] args) throws Exception {
//loading allQuestions
allQuestions = loadQuestions();
//creating the session
SlackSession session = SlackSessionFactory.createWebSocketSlackSession(TOKEN);
//adding a message listener to the session
session.addMessagePostedListener(QuizzBot::processMessagePostedEvent);
//connecting the session to the Slack team
session.connect();
//delegating all the event management to the session
Thread.sleep(Long.MAX_VALUE);
}
Message processor structure
The processor has to handle two kinds of messages: requests for a quiz and answers during a quiz. Here’s the code that lets it do so:
private static void processMessagePostedEvent(SlackMessagePosted event,
SlackSession session) {
handleQuizzRequest(event, session); handleAnswer(event, session);
}

 

Handling quiz requests

Quiz requests are triggered using !quizz. The event handler below will first check whether the message contains this keyword. If the keyword is found, then the bot has to check whether it is already running a quiz. If so, it asks the user who requested a quiz to wait (the bot can handle only one quiz at a time). If no quiz is currently run, then the bot prepares the quiz data (randomly choosing questions and preparing the scoreboard) to send the first question.

private static void handleQuizzRequest(SlackMessagePosted event, SlackSession session) {
//looking for !quizz command
if ("!quizz".equals(event.getMessageContent().trim())) {
// check if a quiz is currently in progress on a channel
if (quizzChannel != null) {
session.sendMessage(event.getChannel(), "I'm sorry " + event.getSender().getRe ", I'm currently running a quiz, please
wait a few minutes");
return;
}
prepareQuizzData(event); sendNewQuestion(session);
}
}

 

Handling quiz answers

Quiz answers are digits between 1 and 4; any other values are ignored. (If no quiz is running, the bot will ignore all messages.) Whatever the answer given, the respondent’s user ID is stored in order to avoid having one user give two answers. Depending on whether the answer is right or wrong, the bot will either give a point to the user and trigger the next question (or, if that was the last question, display scoreboard) or indicate to the user that it was a wrong answer. Here’s the code that handles all of this:

private static void handleAnswer(SlackMessagePosted event, SlackSession session) {
//no quiz launched
if (quizzChannel == null) {
return;
}
//an answer should be given on the quiz channel only
if (!event.getChannel().getId().equals(quizzChannel.getId())) {
return;
}
//an answer is a single digit from 1 to 4
String answerValue = event.getMessageContent().trim();
if (!ACCEPTED_ANSWERS.contains(answerValue)) {
//ignore answer
return;
}
int currentCounter = questionCounter;
synchronized (lock) {
if (questionCounter != currentCounter) {
// the question has timed out and the next one was sent by the bot
return;
}
//A user can answer only once per question
if (answeredUser.contains(event.getSender().getId())) { session.sendMessage(quizzChannel, "I'm sorry " +
event.getSender().getRealName() +
", you can only give one answer per question");
return;
}
//This user has now given an answer
answeredUser.add(event.getSender().getId());
//Check value
if (Integer.parseInt(answerValue) == expectedAnswer) {
//good answer
goodAnswer(event, session);
} else {
wrongAnswer(event, session);
}
}
}

 

Sending a new question

When the quiz starts or when a player gives a correct answer, the bot sends a new question. To achieve that, the bot picks from its randomized question list and prepares a Slack attachment to nicely format the question. Then it sends the question on Slack and starts a timer to wait up to 10 seconds; if no correct answer is given by the time the 10 seconds run out, the bot sends a new question. Here’s the code that does all of this:

private static void sendNewQuestion(SlackSession session) {
//resetting the users who have answered to this question
answeredUser = new HashSet<>();
Question currentQuestion = shuffledQuestions.get(questionCounter);
expectedAnswer = currentQuestion.expectedAnswer;
SlackAttachment attachment = new SlackAttachment(currentQuestion. question,
currentQuestion. question, "", "");
attachment.addField("1", currentQuestion.answer1, true); attachment.addField("2", currentQuestion.answer2, true); attachment.addField("3", currentQuestion.answer3, true); attachment.addField("4", currentQuestion.answer4, true); session.sendMessage(quizzChannel, "", attachment);
timer = buildTimer(session); timer.start();
}

 

Handling a correct answer

When a player chooses the right answer, the timer is interrupted (since the bot was waiting for a correct answer to be given) and a point is given to the player. Next, the bot tells the user he gave the right answer and triggers the next step: either posting either a new question or (if that was the last question) the final results of the quiz. Here’s the code that handles that:

private static void goodAnswer(SlackMessagePosted event, SlackSession session) {

timer.interrupt(); increaseScore(event);

session.sendMessage(quizzChannel, "Good answer " + event.getSender().getRealName());

nextQuizzStep(session);

}

 

Handling a wrong answer

When someone makes an incorrect guess, that player is registered in the scoreboard, since he has participated; even if the answer is wrong his score will be displayed at the end of the quiz. Then a message is sent to that used to tell him that his answer was wrong. Here’s the code that accomplishes these two steps:

private static void wrongAnswer(SlackMessagePosted event, SlackSession session) {

registerPlayerInScoreBoard(event); session.sendMessage(quizzChannel, "I'm sorry, you're wrong " +

event.getSender().getRealName());

}

 

Displaying the quiz result

At the end of the quiz, the result is displayed using an attachment. The score is stored in a map associating the player’s user id to his score, so the bot adds a field for each participating player. The bot then sends the attachment with an empty message, and finally resets questions and the quiz channel variable so a new quiz can be requested:

private static void showResults(SlackSession session) { SlackAttachment attachment = new SlackAttachment("Final score",
"Final score", "", ""); for (Map.Entry<String, Integer> entry : score.entrySet()) { attachment.addField(session.findUserById(entry.getKey()).getRealName(),
entry.getValue().toString(), true);
}
session.sendMessage(quizzChannel, "", attachment); shuffledQuestions = null;
quizzChannel = null;
}

In all of the Java Slack bots presented in this blog, you’ve seen how to send messages, read files, create files, and pin reaction emo‐ jis. This is a good start to give you some ideas to implement your own hacks.

 

Over the course of the more than 50 hacks in this blog, we have detailed the many ways that Slack can benefit you and your company or community. Through integrations, extensions, and bots, we have pointed you in the right direction so you can take advantage of your own Slack implementation, and we have provided you with

Recommend