Edit in November 2015: The new URL is http://popularfollowers.raymondcamden.com

Almost two years ago I announced the launch of a new site. It was built to display a report of your followers sorted by how many followers they had. Now - I completely recognize that this isn't necessarily an important report. But it was something I was curious about and I thought it would be cool. I'm being followed by two stars of Young and the Restless (my secret obsession), one of my favorite authors, and best of all, Game of Thrones.

Unfortunately, Twitter killed off almost all the public APIs it has and now requires a heck of a lot more work (comparatively so anyway) to use their API. Why? I assume to help keep their servers up. Either that or Twitter hates simple. (And kittens.) A while ago I decided to take a stab at rebuilding it as a server-side application in Node.js. I found an awesome article on it (How To Use OAuth and Twitter in your Node.js / ExpressJS App) and was quickly able to add OAuth to my application.

Unfortunately, even with access to the API now what I wanted to do still wasn't necessarily easy. I begin by getting the IDs of your followers. That's simple enough although it maxes out at 5000 at a time. But then I need to get details about your followers. You are allowed to get 100 at a time. So for someone like me, nearing 8000 followers, that's about 80 calls. It is still within the API limits (since it should be per user using OAuth), but it is approaching the max. For folks curious, here is the code I used just for this process.

function getIds(screen_name, sess, cb, data, start) {
	if(!data) data=[];
	if(!start) start = '-1';

	var oa = new OAuth(sess.oa._requestUrl,
	                  sess.oa._accessUrl,
	                  sess.oa._consumerKey,
	                  sess.oa._consumerSecret,
	                  sess.oa._version,
	                  sess.oa._authorize_callback,
	                  sess.oa._signatureMethod);
	
	oa.get('https://api.twitter.com/1.1/followers/ids.json?cursor='+start+'&screen_name='+screen_name+'&count=5000&skip_status=1', sess.oauth_access_token, sess.oauth_access_token_secret,            
      function (e, retData, ores) {
		if (e) {
			console.log('getIds: error result');
			console.dir(JSON.parse(e.data));
			/*
			var error = JSON.parse(e.data).errors;
			res.send({error:1, message:error[0].message});
			*/
		} else {
			console.log('get ids done');
			retData = JSON.parse(retData);
			data = data.concat(retData.ids);
			//console.dir(data);
			if(retData.next_cursor) {
				getIds(screen_name, sess, cb, data, retData.next_cursor);	
			} else {
				cb(data);
			}
		}
      });
}

function getFollowers(sess, ids, cb, data, start) {
	if(!data) data=[];
	if(!start) start = 0;
	var idSlice = ids.slice(start,start+100);
	var sn = idSlice.join(',');
	var oa = new OAuth(sess.oa._requestUrl,
	                  sess.oa._accessUrl,
	                  sess.oa._consumerKey,
	                  sess.oa._consumerSecret,
	                  sess.oa._version,
	                  sess.oa._authorize_callback,
	                  sess.oa._signatureMethod);
	
	oa.get('https://api.twitter.com/1.1/users/lookup.json?user_id='+sn, sess.oauth_access_token, sess.oauth_access_token_secret,             
      function (e, retData, ores) {
		if (e) {
			console.log('getFollowers: error result');
			console.dir(JSON.parse(e.data));
			
			var error = JSON.parse(e.data).errors;
			cb({error:1, message:error[0].message});			
		} else {
			retData = JSON.parse(retData);
			console.log('got '+retData.length+ ' items');
			
			//All we need is username + followercount
			for(var x=0, length=retData.length; x<length; x++) {
				data.push({screen_name:retData[x].screen_name, followers:retData[x].followers_count,profile_image_url:retData[x].profile_image_url});
			}
			//data = data.concat(retData);
			console.log('size of data is now '+data.length + ' and ids len is '+ids.length);
			//console.dir(data);
			if(data.length < ids.length && data.length < 10000) {
				getFollowers(sess, ids, cb, data,start+retData.length);	
			} else {
				cb(data);
			}
		}
      });
	
};

Note I use a simple memory-based cache so it doesn't have to refetch the data on reload. Speaking of - there is a bug currently with the site where the request will time out on the client for people (again, like me) with lots of followers. In fact, it always does this for me on my first load, but just on the production server. When I reload, the server returns the data immediately as it apparently kept churning away on the back end. This app is not perfect yet, and probably never will be. I built it for fun.

So, want to check it out? Just head over to popularfollowers.raymondcamden.com and give it a spin.