Resurrecting Shakespeare using Node, Express, Twilio Add-Ons, IBM Watson and Love

February 13, 2017
Written by

shakespeare

Valentine’s day isn’t my favorite holiday, mostly because I never seem to get my act together before the big day. But even though the holiday leaves me confused and clueless, the thought of adding a little more love into the world never does. So this year, instead of planning a wonderful night (like maybe I should have done?), I instead opened my laptop and decided to resurrect a 400 year-old poet who had a lot to say on the subject of love— Billy H. Shakespeare.

The thinking was simple, since I’m notoriously bad at writing romantic cards, maybe Shakespeare could do some of the heavy lifting for me. In short I wanted to create an artificially intelligent Shakespeare bot that would:

  • Tell me if my romantic prose was good or awful
  • Create a custom sonnet for me on demand

Worst-case scenario was that it would give me a good excuse to play with the Twilio-IBM Watson integration; best-case scenario was that it would save my Valentine’s day!

You can try out the finished product now by texting “Help Me Shakespeare!” to (501) 712-4613.

#HelpMeShakespeare

You can see the full code on github, but let’s walk through some of the challenges of resurrecting Shakespeare.

Writing a sonnet from scratch

The first custom piece we need to figure out is how to generate Shakespeare poetry. Luckily there is an awesome API called PoetryDB which allows us to search and filter by poet. Right in the documentation there was an example that searched for Shakespeare sonnets by filtering the results to only poems that were exactly 14 lines long.

 

var sonnetGenerator = {};

sonnetGenerator.randomSonnet = function(user) {
  var client = new Client();
  var poem = [],
    sonnet = '';
  // Search poetryDB for all shakespeare sonnets. 
  client.get("http://poetrydb.org/author,linecount/Shakespeare;14/lines", function (data, response) {
      for (var i = 0; i < 14; i++) {
      var line = data[getRandomInt(data.length)].lines[i];
      poem.push(line);
    }
    // Generate an image of the sonnet to make it easily shareable.
    sonnet = createImage(poem, user);
    return;
  });
}

 

But since these sonnets would make for fairly long text messages, which would be pretty hard to send to a special someone, we should try and generate an image of the poem. So to do that let’s use node-gd and generate a simple gif of our new rad poem.

 

var createImage = function(poemArray, user) {
  var baseY = 336,
    leading = 50,
    pid = getRandomString(),
    path = `./public/shakes/shake${pid}.gif`,
    publicUrl = `http://45.55.136.193/shakes/shake${pid}.gif`,
    poemString = poemArray.join("\r\n");

  // Use a template gif as the starting point
  gd.openFile('./public/shakespeare.gif', function(err, img) {
    if (err) {
      throw err;
    }
    
    // set text colors
    var txtColor = img.colorAllocate(13, 18, 43);
    var txtColor2 = img.colorAllocate(241, 47, 70);

    // Setup fonts and signature
    var fontPath = './public/fonts/whitney-book.otf';
    var signature = `  - Love ${user.firstName}`;
     
    // Since we can't set a leading value for the type, let's insert 
    // the poem lines, line by line at the specific distance
    for (var i = 0; i < poemArray.length; i++) {

      // calculate the y position using our leading value
      var newY = baseY + (leading * i);

      // insert line onto image canvas
      // (color, fontPath, fontSize, fontAngle, x, y, string)
      img.stringFT(txtColor, fontPath, 21, 0, 77, newY, poemArray[i]);
    }

    // insert signature after the poem
    img.stringFT(txtColor2, fontPath, 28, 0, 120, 1096, signature);

    img.saveFile(path, function(err) {
      // once the image actually exists on our server, 
      // then we can send it
      user.sendImage(publicUrl);

      // save it to the users profile
      user.sonnets.push({
        url: publicUrl,
        text: poemString
      })
      user.save();

      // destroy it in memory
      img.destroy();
      if (err) {
        throw err;
      }
    });

  });
  return;
}

Artificial Intelligence

To embed some artificial intelligence into this bot I needed to use Watson’s sentiment analysis to determine whether a phrase or sentence was positive or not. So let’s add the ability for our Shakespeare-bot to check the positivity of a message. Depending on whether or not it’s positive, we either send an uplifting message or we send a discouraging note with a new custom sonnet.

 

var romanceMeter = {};

romanceMeter.check = function(user, req) {
  var results = JSON.parse(req.body.AddOns);
  var sentiment = results.results.ibm_watson_sentiment.result.docSentiment,
  	positive = messages[getRandomInt(messages.length)],
  	snark = snarky[getRandomInt(snarky.length)],
  	message = '',
  	media = '';

  if (sentiment.score > 0.4) {
  	message = `Well done ${user.firstName}, you cherub! Thou hast created quite the romantic message. ${positive}`;
  } else {
  	message = `Oof ${user.firstName}. ${snark} May I recommend thou uses a sonnet of mine own... writing it now.`;
  	sonnetGenerator.randomSonnet(user);
  }
  return {
  	'message': message,
  	'media': media
  }
};

 

It turns out there is a fairly high correlation between a positive message and a generally nice message so it seems to work fairly consistently.

Notice that to grab the sentiment data in node, we first needed to JSON.parse() the string that was returned inside of req.body.AddOns we had to do this because even with body-parser extended, express still does not create javascript objects for the nested objects in the Twilio response.

 

So let’s recap, we have an AI-driven bot that analyzes my romantic dexterity, generates custom Shakespeare poems and creates shareable images of those poems. Now all we need to put a bow on this is a user database, some routing in express and some logic to switch responses based on user input. To see how the code that handles all of this and more, go to github.

Otherwise enjoy your weekend, and if you have any questions about this app, how my valentine’s day ended up going, or you just want to dream up the next weird Twilio app– shoot me a note on twitter I’m always happy to jam.