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.)