Create a Chatbot in Python from Scratch

Create a Chatbot in Python

Creating Chatbots in Python

In this blog, you will learn how to install a chatbot and create a full chatbot using Python 3 from Scratch. And then explain how to connect and install chatbot with slack.

 

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, err-bot 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.

 

Connect err-bot to Slack

Let’s set up err-bot so you can take advantage of the hacks in this blog that use it. As mentioned earlier, err-bot is a chatbot that is written and is extensible in Python. 

 

Err-bot is a daemon process that can connect to Slack and converse with you and your team. Since err-bot can install dependencies automatically, we recommend you install it into a Python virtualenv. 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:

  1. It’s chat system agnostic: write your plugin once, and run it on every supported chat system.
  2. Its plugin repos are just Git repos.
  3. It has a very gentle learning curve.
  4. Its features include presence, mentions, cards, conversation flows, automation, and more.

 

Prerequisites

We recommend that you use the following to install Errbot:

  1. Linux, Mac OS, or Windows
  2. Python 3.4+
  3. virtualenv

 

Windows and Python 3 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 err-bot 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 or the standard virtualenv tool explained as an alternative below.

 

Once you have virtual wrapper 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 the virtual wrapper, you can use the standard virtualenv. 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 err-bot on it. Pip, a tool used to install and manage Python software packages, 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 [...]

 

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 err-bot to connect to Slack. 

 

Now, create a root directory for your err-bot instance; this is where it will store its data, configs, and logs. err-bot will also automatically create a subdirectory for your plugins in development:

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

 

Using err-bot locally in a graphics mode (optional)

In addition to text mode (specified by the -T flag on the command line), err-bot also has a graphics mode (-G) that can display images, autocomplete commands, etc.

 

This mode, like Text mode, is a development model that’s useful for iterating quickly while you’re developing plugins because err-bot 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

 

Connect err-bot to Slack

In order to connect err-bot 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 err-bot 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 err-bot is set up and invited to your Slack channel, you’re ready to start err-bot with no parameter:

(errbotve) $ cd ~/err-bot(errbotve) $ err-bot[...]

The bot user’s status-indicator circle should turn green in the chat-room you added err-bot, 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

Err-bot 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 err-bot 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, err-bot 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 err-bot 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 err-bot that can generate ASCII art. To do this, we will use Errbot’s plugin-creation wizard.

 

First, go to your plugins directory (~/err-bot/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

$ err-bot--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 err-bot 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 err-bot 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 err-bot 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 err-botimport BotPlugin, botcmd
from pyfiglet import Figlet
class Big(BotPlugin):
"""This plugin will make err-botdisplay 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 err-bot 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 err-bot will install them when you execute the command !repos install

 

Next, start err-bot in Text mode and it should respond to your new command:

$ err-bot-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 err-bot 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 err-bot 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 err-bot 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 err-botimport 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 err-botimport 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 err-bot say something in a team channel.

 

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

from err-botimport BotPlugin, botcmd
class Startle(BotPlugin): @botcmd(split_args_with=' ')
def say(self, _, args):
""" Make err-botsay 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 err-bot parse the input of the incoming message, instead of just sending it as a string in args. err-but 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 err-bot 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 err-but 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 err-bot 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 err-bot 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 err-but 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. 

>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 err-botimport 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, 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 err-bot being written in Python is its huge ecosystem of bindings and libraries

To install a complete version of this plugin, run the command! repos install er charts.

 

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.matplotlib 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 err-bot--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 err-botimport BotPlugin, arg_botcmd # err-botbase.
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, 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 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 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 err-bot 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 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 random 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 err-bot 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. 

 

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 a library called Beautiful soup. This library has a very intuitive API to navigate through structures like this. 

 

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 err-bot command. Doing so is trivial at that point:

from err-botimport 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 err-bot-T:

$ err-bot-T [,,,]

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

 

And finally on Slack with the command err-bot.

This hack showed you how to extract a feature from an existing webpage and expose it as an Ernbot command on Slack. This concludes 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 my hubot 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.

 

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 system 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:

  1. adapters are pieces of glue code that connect Lita to a specific type of chat system.
  2. handlers add a chat/command feature.
  3. 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 [...]
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. 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.

Recommend