Welcome to part three of this series. In this post I'm going to show how easy it is to turn what is currently a completely useless bot into something genuinely useful to you and your team.

Why Use a Bot?

We interact with many systems every working day, and very often we are doing this as part of a larger team.

Bots in whatever shape or form allow us to coordinate, automate, and enrich communication amongst our team.

In this example we will build in the ability for our bot to notice a bug ID within a message and return some data about that bug.

The 10,000 Foot View

Our scripts (that provide functionality) read lines of user input and return an output (if applicable). One line from the user can turn into many lines of output, or none at all.

Right now our bot is very simple in the fact that any script that blocks for a considerable amount of time will delay any lines being returned as well as new lines being processed.

Lookin' for a Bug ID

In this example we're going to work with the Redmine bug tracker. Redmine has been a great fit for our team and apart from being a little difficult to initially set up, has been running smoothly since and provides us with all of the features that we require in our day to day work.

Luckily for us Redmine also has a pretty straightforward JSON API to work with. We can call a specially crafted URL from PHP that will give us information about a particular bug ID, just what we want!

To cut right to the chase, here's the URL that we will be using to find information on a particular bug:

http://yourredmineinstancehere/issues/{ID}.json

To work with the API we need to create a valid API key in the Redmine UI that we will send along in our request later.

The code itself is very short, I'll start with the ZeroMQ framework for a script:

<?php
define('DCBOT_CORE', 'tcp://localhost:5555');

$context = new ZMQContext();

$core = new ZMQSocket($context, ZMQ::SOCKET_REQ);
$core->connect(DCBOT_CORE);

// Set up a socket for this script to listen on.
$script = new ZMQSocket($context, ZMQ::SOCKET_REP);
$script->bind('tcp://*:5556'); // Note that this corresponds to the line in core.json

while (true) {
  // Receive a message from the core.
  $msg = $script->recv();

  // Setup our return value which will contain zero or more lines to give to the user.
  $ret = array();

  //
  // HANDLE BUGS HERE
  //

  $script->send(json_encode($ret));
}

See? Not much to it and very similar to our previous examples. Now on to the interesting part of listening out for a bug ID, first of all we need to add a couple of important configuration lines at the top of the file:

<?php
define('REDMINE_ROOT', 'http://yourredmineinstancehere/'); // Make sure you include a trailing slash.
define('REDMINE_API_KEY', 'yourkeygoeshere'); // This is the API key you generated from the Redmine UI.

Once this is done we can place the actual code at the HANDLE BUGS HERE section:

<?php
$matches = array();
if (preg_match('/#(\d+)/', $msg, $matches) == true) {
  // We should now have an ID in $matches[1].

  // Set up CURL to fetch the JSON for this issue ID.
  $ch = curl_init(REDMINE_ROOT . "issues/{$matches[1]}.json");

  // Make sure that we pass in our API key or we will get an error response.
  curl_setopt($ch, CURLOPT_HTTPHEADER, array(
    'X-Redmine-API-Key: ' . REDMINE_API_KEY
  ));

  // Return the URL contents in a variable for us to use.
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);

  // Grab the contents.
  $res = curl_exec($ch);

  curl_close($ch);

  if (!empty($res)) {
    $res = json_decode($res);

    // Build the output for the transport.
    $ret[] = "{$res->issue->project->name}: {$res->issue->subject}";
    $ret[] = "({$res->issue->tracker->name}, {$res->issue->status->name}) " . REDMINE_ROOT . "issues/{$matches[1]}";
  }
}

If you fire up all of the parts of the bot now you should find that when someone mentions an ID (ie. "We have now closed #86") that the bot picks up on this and responds with some information about the bug and a link. Convenient!

Well this pretty much wraps up the mini-series on decoupling with ZeroMQ. Sorry it took so long for me to publish but I hope someone got something useful out of it in the end!


(Maybe) Related posts: