Grabbr

A simple Node.js app for emailing me my Twitter favourites from the day.

24 February 2013

I use Twitter everyday. But as the number of people I know and follow has grown, it's become hard to keep up on everything that's going on.

To try and remedy this, I would favourite things that I'd want to read later. Articles, videos, humouress cartoons - Anything that takes a little too long to justify reading or watching them at work. But by the time I get home, I was either too tired to look them up or would just plain forget to do so which makes the whole favouriting thing pointless. You've got things like Pocket and Readabilty which do a fine job of storing stuff for later but I needed a prompt.

So how do I go about prompting myself to do something. The one thing I still give a fair amount of attention to, regardless of content, is my email and on my iPhone, my email gives me a prompt. So I figured I'd write an app that would run all day, Collate the things I had favourited from that day and then send off a summary to me around the time I've gotten home and had my evening Tea.

It's not a massively complicated app, I decided to write it in Node because I hadn't yet dealt with leaving something to just run in the background rather than on an event. I was curious to play with this dynamic. The first thing to do was to set up access to Twitter. NPM has a library for that so you can create a connection quite easily like so:

var twit =  new twitter({
	consumer_key : 'CONSUMER_KEY',
	consumer_secret : 'CONSUMER_SECRET',
	access_token_key : 'ACCESS_TOKEN_KEY',
	access_token_secret : 'ACCESS_TOKEN_SECRET'
});

Once you have the connection, you can start making queries to the endpoints.

twit.get('http://api.twitter.com/1.1/favorites/list.json', {screen_name : username, include_entities : true}, function(data){console.log(data);})

Simples. Now that we can get data, We need to be able to decide when to get data. I don't want an hourly update, I want a daily update. Like an evening newspaper on a train - I can take it or leave it. My timer function is nothing fancy. Before the app is initialised I can set an hour that I want it to be sent (19), then in a setInterval, the time is checked hourly. If the hour of delivery is upon us it will trigger the getFavourites() function, If not, it will bide its time.

var whenDoYouWantThese = 19;
var currentTime = new Date();
var today;
var lastSent;
var sent = false;
var inProgress = false;
var delay = 1 * 1000 * 60 * 50;

setInterval((function(){
	today = currentTime.getDate();
	if(currentTime.getHours() == whenDoYouWantThese){
		if(inProgress == true){
			console.log("It's in progress. Have some patience, would you?");
		} else if(sent == false && inProgress == false){
			inProgress = true;
			getFavourites('seanmtracey');
		} else if(sent == true && lastSent < today){
			getFavourites('seanmtracey');
		} else {
			console.log("Keep Waiting, It's not time yet. They've already been sent.");
		}
	} else {
		console.log("Keep Waiting. \"Tick Tock\", Goes the clock.")
	}

}),  delay);

Once the getFavourites() function is called the first thing it does is load a .txt file of all of the previous favourites it has grabbed. If I had included a parameter in the Twitter APi request specifying a time that the favourite cannot be older by then this step wouldn't be necessary but I'm not entirely sure of what's available in the 1.1 API so we're going to do it anyways.

fs.readFile('previousFavourites.txt', 'utf8', function (err,data) {
		if (err) {
			return console.log(err);
		} else {
			if(data.length > 1){
				previousFavourites = JSON.parse(data);
			}
		}
	});

Now that we know what we've already sent out, we can request the new favourites and iterate through them to check which ones to send.

for(var x = 0; x < data.length; x += 1){
		if(data[x].entities.urls.length > 0){

			var sentBefore = false;

			for(var c = 0; c < previousFavourites.length; c += 1){

				if(data[x].entities.urls[0].expanded_url === previousFavourites[c]){
					sentBefore = true;
					break;
				}

			}

			if(!sentBefore){

				numberOfArticles += 1;
				(function(data, x){
					//You can include scripts to be loaded in the winow object here					jsdom.env(data.entities.urls[0].expanded_url/*, ["http://code.jquery.com/jquery.js"]*/,function (errors, window) {
						try{
							window.document.title;
						} catch(error) {
							console.log("There was an error...\n\n" + error + "\n\n...Trying again...");
							getFavourites(username);
							return false;
						}

						console.log(window.document.title);

						previousFavourites.push(data.entities.urls[0].expanded_url);
						articleURLS.push(new potentialArticle(window.document.title, data.entities.urls[0].expanded_url, data.id, data.user.name));
					});

				})(data[x], x);

			}
		}
	}

You'll notice in the code that we're loading the web pages of those sites we've favourited. Often tweets aren't very descriptive of the links they share. By grabbing the title from the site we can inclide a little more info in the email. Because the requests are asynchronous, the function is going to return before all of those pages have loaded. By creating a setInterval which checks the number of articles expected against the number of articles retrieved, we can be sure that when we go to construct the email, we'll have all of the relevant information we need.

Once we've gotten all of that data, We can call the mailArticles function. Because my computer wasn't set up to send email, I used the nodemail package to use my Gmail account to send out the email. Creating a transport is as easy as:

var smtpTransport = nodemailer.createTransport("SMTP",{
	    service: "Gmail",
	    auth: {
	        user: "USER EMAIL",
	        pass: "USER PASS"
	    }
	});

Then we can start building the HTML for the email - because plaintext is just dull.

	var html = "";

	html += "
" for(var z = 0; z < articleAddresses.length; z += 1){ html += "
" + articleAddresses[z].pageTitle + "
Posted by: " + articleAddresses[z].username + "
Done.
"; } html += "
"

Once that's done, we can adjust some properties in the mail header and then send it on its merry way.

var mailOptions = {
	    from: "grabbr@sean.mtracey.org", // sender address
	    to: address, // list of receivers
	    subject: "Todays favourites", // Subject line
	    text: "Well, This has gone rather well!", // plaintext body
	    html: html // html body
	}

	// send mail with defined transport object
	smtpTransport.sendMail(mailOptions, function(error, response){
	    if(error){
	        console.log(error);
	    }else{
	        console.log("Message sent: " + response.message);
	    }

	    smtpTransport.close();
	    sent = true;
	    lastSent = currentTime.getDate();
		inProgress = false;
	});

Lovely. Now I can enjoy the days articles with my cuppa.