Deliver XKCD To Your Phone with Node and Twilio

November 06, 2014
Written by

xkcd_phone

Who doesn’t love XKCD? I know I do. The comic appeals to us engineers since it draws humor from some of the nerdiest math and science subjects. As much as I love to read XKCD, it frequently gets lost in the many RSS feeds in my reader which means I miss new comics as they are published. When Twilio released support for MMS on US and Canadian phone numbers I thought what better use for MMS than to let me push XKCD comics right to my phone, as soon as they are published.

In this post I’ll show you how I built a simple Node application that lets me deliver XKCD comics to my phone via Twilio MMS. The app allows me to request and receive specific XKCD comics, and have newly published comics automatically sent to my phone.

Want to give it a try? Send a text message with the ID of your favorite XKCD to +1 201-654-3624. Need some ID suggestions? Try sending 325, 285 or 149.

Expressions in our Formula

To build the app I’m going to use Node.js and the Express web framework to combine together several different services including:

  • Twilio – To send the XKCD comic to my phone I’ll use an MMS-enabled phone number from Twilio. Don’t have a Twilio account? Go sign up for a free!
  • SuperFeedr – To monitor the XKCD Atom feed for new comics, I’ll use Superfeedr. Superfeedr will notify me when a new entry is added to the feed by requesting a URL that I’ll expose from my app.
  • Firebase – Finally, to keep a list of phone numbers that want to receive the new XKCD comics as they are published, I’ll use Firebase.

Can’t wait for me to explain the code? Grab the completed app from GitHub and get started sending XKCD today. Otherwise read on and I’ll walk you through how I built it.

Comics On Demand

I started building my comic delivery application by creating an endpoint which accepts a string representing the ID of a specific XKCD comic from an incoming text message, looks up that comic from xkcd.com and returns the comic image, title and alt text as a MMS message.

diagram

Lets start building the application by running the npm init command:

npm init

This will create a new package.json file with the basic configuration for our application.

Now lets add the set of dependencies that the application will use. You can do this using npm by running the following command:

npm install express body-parser request twilio --save

This command installs the express, body-parser, request and twilio pages from npm and populates the dependency section of the package.json file for us.

Once the dependencies are installed its time to write the application code.

Start by creating a new file named app.js and declaring which libraries are required in this file:

var express = require('express'),
    bodyParser = require('body-parser'),
    request = require('request'),
    twilio = require('twilio');

With the required libraries added, create a new instance of Express. Since requests coming into the application will contain both json and form-encoded data, we can configure the Express app to use the body-parser library to parse that data for us:

var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

Finally start an instance of the Express application:

var server = app.listen(process.env.PORT || 3000, function() {
     console.log('Server started!');
});

At this point feel free to run your application and make sure the server starts. You’ll know its running correctly if you see the text “Server started!” output to the console.

With the Express application configured and started we can create a new route named process. As the name implies, this route will process incoming text messages from Twilio.

The code in this route checks to see if the text message contains a number and if it does, uses the Request HTTP client to get a JSON representation of that specific XKCD comic:

app.post("/process", function(req, res) {
    var body = req.body.Body.trim().toLowerCase();

     var twiml = new twilio.TwimlResponse();
     res.type('text/xml');

     if ( parseInt(body) ) {
          request('http://xkcd.com/' + body + '/info.0.json', function (error, response, json) {

               if (!error && response.statusCode == 200) {
                    var result = JSON.parse(json);
                    twiml.message(function() {
                         this.media(result.img).body(result.safe_title + '\r\n' + result.alt + '\r\nhttp://xkcd.com/' + body);
                    });
               } else {
                    twiml.message('Could not find an XKCD with that number');                                           
               }
               res.end(twiml.toString());
          });
     } else {
          twiml.message('Welcome to XKCD SMS. Text your favorite XKCD ID to have it delivered to your phone.');
          res.end(twiml.toString());
     }
}

If a JSON representation for that ID is found the application uses the Twilio Node helper library to generate and return TwiML that tells Twilio to respond to the inbound text message with an MMS message containing the comics image, title and text.

If the incoming text message did not contain a number or no XKCD comic was found for the number received, then a response message telling the user to try again is generated and sent back to the user.

Thats it! All of the code needed to request delivery of a specific XKCD comic is now in place. All that is left is to point a Twilio phone number at this newly created URL. To do this we will buy a new MMS-enable US or Canadian phone number and configure its Message Request URL with the URL of the application.

But wait! How is Twilio going to be able to make a request to a node website running on your local computer? Through the magic of ngrok which we can use to expose our newly created website out to the internet via a public URL.

11-4-2014 3-56-02 PM

Grab the URL assigned by ngrok and set it as the Message Request Url of your Twilio phone number:

11-4-2014 3-51-49 PM

Awesome! If you are following along you should now be able to receive XKCD comics on your phone. Send a text message containing your favorite XKCD comic number to your Twilio phone number and if its a valid XKCD comic ID, you should receive a response containing the comic image, title and alt text.

Comic Delivery

Sending and receiving specific XKCD comics is pretty cool, but it still does not solve my original problem of missing newly published comics. To fix that, lets modify our application to be a bit more proactive and have it push newly published comics directly to me, or anyone else who wants subscribe.

To do this we’ll need to add two new pieces to the application. First we’ll need a way to store a list of users who want to receive new comic deliveries. For that we’ll use Firebase. Second, we’ll need a way to watch and be notified when a new XKCD comic is published. For that we’ll use a cool service named SuperFeedr.

Lets start by modifying our application to allow users to subscribe to new comic deliveries by storing their phone number in Firebase. Begin by adding the Firebase package to the application:

npm install firebase --save

With the Firebase package installed in our application we can require it in the app.js file:


var express = require('express'),
    bodyParser = require('body-parser'),
    request = require('request'),
    twilio = require('twilio'),
    Firebase = require('firebase');

Next, we need to get the list of phone numbers that we already have stored in Firebase. To do this create a new instance of the Firebase client, passing in the URL that points to the location in Firebase where you are storing your list of phone numbers.

Notice that I’ve also attached an event handler that will be called whenever a new phone number is added to Firebase.


var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

var numbers = [];

var subscribersRef = new Firebase(process.env.FIREBASE_URL);
subscribersRef.on('child_added', function(snapshot) {
     numbers.push( snapshot.val() );
     console.log( 'Added number ' + snapshot.val() );
});

With the list of phone numbers received, we can modify the existing process route to accept the new command ‘subscribe’, which tells the application to save the users phone number into Firebase if its its not already there.


if ( parseInt(body) ) {
     request('http://xkcd.com/' + body + '/info.0.json', function (error, response, json) {

          if (!error && response.statusCode == 200) {
               var result = JSON.parse(json);
               twiml.message(function() {
                    this.media(result.img).body(result.safe_title + '\r\n' + result.alt + '\r\nhttp://xkcd.com/' + body);
               });
          } else {
               twiml.message('Could not find an XKCD with that number');                                           
          }
          res.end(twiml.toString());
     });

} else if(body  === 'subscribe' ) {
     var fromNum = req.body.From;
     if(numbers.indexOf(fromNum) !== -1) {
          twiml.message('You already subscribed!');
     } else {
          twiml.message('Thank you, you are now subscribed. Reply "STOP" to stop receiving updates.');
          subscribersRef.push(fromNum);
     }
     res.end(twiml.toString());
} else {
     twiml.message('Welcome to XKCD SMS. Text your favorite XKCD ID to have it delivered to your phone.  Text "Subscribe" receive updates.');
     res.end(twiml.toString());
}

Fantastic. We’ve now got a list of phone numbers that we’ll send new comics to as they are published. Now we just need to set up SuperFeedr to let us know that happens.

Configuring SuperFeedr is easy. Once logged in to the site open the PubSubHubbub Console tool and make sure Mode is set to Subscribe. Drop in the URL for the XKCD Atom feed and then add a URL that points to a new route in our application named relay. We’ll create that new route in just a bit. Finally check the Convert to JSON box.

11-4-2014 4-08-08 PM
Click Submit and now SuperFeedr is watching the XKCD Atom feed for new entries. When a new entry is added SuperFeedr will make an HTTP request to the relay route in our application passing the new Atom entry formatted as JSON.
"items": [
    {
      "id": "http://xkcd.com/144/",
      "published": 1414893240,
      "updated": 1414893240,
      "title": "Turnabout",
      "summary": "<img src=\"http://imgs.xkcd.com/comics/turnabout.png\" title=\"Whenever I miss a shot with a sci-fi weapon, I say 'Apollo retroreflector' really fast, just in case.\" alt=\"Whenever I miss a shot with a sci-fi weapon, I say 'Apollo retroreflector' really fast, just in case.\"/>",
      "permalinkUrl": "http://xkcd.com/144/",
      "standardLinks": {
        "alternate": [
          {
            "title": "Turnabout",
            "rel": "alternate",
            "href": "http://xkcd.com/144/",
            "type": "text/html"
          }
        ]
      }
    }]

Its the job of the relay route to take the new comic notification data from SuperFeedr, parse it and then send out MMS messages to all of the subscribers in our list of phone numbers.

Even though we configured SuperFeedr to send us JSON, some of the Atom content that we need remains formatted as XML. To parse that remaining XML, we need to add one final package you our application named xml2js:

npm install xml2js --save

In app.js require the library:


var express = require('express'),
    bodyParser = require('body-parser'),
    parseString = require('xml2js').parseString,
    request = require('request'),
    twilio = require('twilio'),
    Firebase = require('firebase');

With xml2js installed we can add the new relay route to the app.

app.post('/relay', function(req, res){

     var client = new twilio.RestClient(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);   

     req.body.items.forEach(function(item) {

          var item = req.body.items[i];

          parseString(item.summary, function (err, result) {

               if (!err) {
                    var title = result.img.$.title;
                    var src = result.img.$.src;

                    for(var j=0;j < numbers.length; j++) {
                         client.sendMessage({
                              to: numbers[j], 
                              from: process.env.TWILIO_PHONE_NUMBER, 
                              body: item.title + ':\r\n' + title + '\r\n' + item.permalinkUrl, 
                              mediaUrl: src
                              }, function( err, message ) {
                                   console.log( message.sid );
                         });
                    }
               } else {
                    console.log(err);
               }
          });
     });
     res.status(200).send();  
});

The code in this route is pretty straight-forward. We loop through all of the new entries sent to us from SuperFeedr and parse the XML snippet included in each, extracting the comics image source and title.

Next we loop through all of the phone numbers stored in Firebase and use the Twilio Node helper library to send each one an MMS containing the comic image, title, text and permalink.

And that’s it. Now each time the XKCD Atom feed adds a new entry, SuperFeedr will tell our application about it and we will send out text messages with the comic to all of our subscribers.

wp_ss_20141104_0001

Wrapping It Up

Congratulations! By combining a little bit of Node and some awesome services like Firebase, SuperFeedr and Twilio, we’ve built an XKCD delivery service. Now I’ll never miss another newly published XKCD again, and neither will you!

Grab the code from GitHub and host your own delivery service, or just send a text message to +1 201-654-3624 to subscribe to mine. And when you do, send me an email or hit me up on Twitter and let me know what your favorite XKCD is.