Building a Twitter bot to display random comic book covers

A bit over two years ago I played around with the (then) recently released Marvel API to build some cool demos. The end result of that experiment was a simple web app that randomly displayed a Marvel comic book cover every minute: http://marvel.raymondcamden.com/.

This weekend I was thinking about a few Twitter accounts I follow that just post random pictures. (I'll share a list of them at the end.) I like these accounts because they're easy to ignore, provide something simple and cool to my feed, and are just a random piece of coolness during the day. I thought it might be kind of fun to build a similar mechanism for comic books (well, Marvel comics, I need to see if DC has an API).

In theory - all I needed to do was:

  • Create a way to select a random cover (which was already done - by me)
  • Create a way to Tweet (there's probably a npm library for that - yep - there is)
  • Create a schedule (there's probably a npm library for that too - yep - there is)

It ended up being very quick to develop - maybe two hours total. Here is the complete source of the main script file. (Note, the entire thing is up on GitHub - the link will be at the bottom.


/*eslint-env node*/

var request = require('request');

var express = require('express');

var credentials = require('./credentials.json');

var Twitter = require('twitter');
var client = new Twitter(credentials.twitter);

var marvel = require('./marvel');
marvel.setCredentials(credentials.marvel.private_key, credentials.marvel.api_key);

// cfenv provides access to your Cloud Foundry environment
// for more info, see: https://www.npmjs.com/package/cfenv
var cfenv = require('cfenv');

var app = express();

app.use(express.static(__dirname + '/public'));

// get the app environment from Cloud Foundry
var appEnv = cfenv.getAppEnv();

// start server on the specified port and binding host
app.listen(appEnv.port, '0.0.0.0', function() {

    // print a message when the server starts listening
    console.log("server starting on " + appEnv.url);
});

var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];

function tweetRandomCover() {
    console.log('First, we get a random cover.');

    marvel.getCover(function(res) {
        console.log('back from mavel');
        console.dir(res);
        var tweet = res.title + ' published '+(MONTHS[res.date.getMonth()])+' '+res.date.getFullYear() +'\n'+res.link;
        
        console.log('Now going to fetch the image link.');

        request.get({url:res.url,encoding:null}, function(err, response, body) {
            if(!err && response.statusCode === 200) {
                console.log('Image copied to RAM');

                client.post('media/upload', {media: body}, function(error, media, response) {

                    if(error) {
                        console.error('Error from media/upload: '+error);
                        return; 
                    }
                    
                    // If successful, a media object will be returned.
                    console.log('Image uploaded to Twitter');

                    var status = {
                        status: tweet,
                        media_ids: media.media_id_string 
                    }

                    client.post('statuses/update', status, function(error, tweet, response){
                        if (!error) {
                            console.log('Tweeted ok');
                        }
                    });

                });
                        
            }
        });
    }); 
}

app.get('/forceTweet', function(req, res) {
    tweetRandomCover();
    res.end('Done (not really)');
});

var cron = require('cron');
var cronJob = cron.job('0 6,12,18 * * *', function() {
    console.log('do the cover');
    tweetRandomCover(); 
    console.log('cron job complete');
});
cronJob.start();

Let's break it down bit by bit, focusing on the important parts. To handle the Twitter API, I used the twitter Node library. As you will see a bit later in the code, it is incredibly trivial to use, even when creating Tweets with media attached.

The Marvel API is just a copy of the code I used before, although I've modified it a bit so I can pass in my credentials.

The real meat of the code is in tweetRandomCover. We begin by asking the Marvel API for a random cover. If you read my post from two years ago you'll note that I have to fake that a bit. I essentially select a random month+year and grab everything I can from there - then select an item.

Once I have the random issue, I use the request library to suck down the binary of the image into a variable. I've heard of this library quite a bit, but I've never actually used it. Big mistake on my part.

Finally - I have to create the tweet. Twitter requires you to upload the media first so it is a two step process. First the image is posted and then the actual Tweet is created. I've got a bit of "Callback Hell" going on here and if this app did anything else I'd abstract this logic out of the main script, but since this isn't a web app people will hit, I'm not going to worry about it.

The final aspect is scheduling - which you can see is done via node-cron. Easy to use - it took me longer to figure out the right cron syntax than it did to implement the code. As you can see, I've selected a schedule that should post tweets three times a day which "feels" right for this kind of account. I may tweak that later.

You can find the complete code (although there's not much else) up on GitHub: https://github.com/cfjedimaster/randomcomicbook. I'm hosting the app up on IBM Bluemix.

And of course, you can (and should!) follow the Twitter acount: https://twitter.com/randomcomicbook

PS...

So yeah - about those random Twitter accounts I follow for pictures? Here they are:

I used to follow some related to historical pictures, but they either turned to spam or shared pictures unrelated to history, which to me is a cardinal sin of these types of accounts. (Another example - news organizations that will RT their sports or entertainment accounts. I freaking hate that.)

Like This?

If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can also subscribe to the email feed to get notified of new posts.