Posting to Twitter with Node.js

I have a site, givit.me, where people can post stuff that they don't need or want, and sell it or give it away (hence the name). The site has yet to take off, but with social sharing, and cheapness of hosting, it can pretty much stay up there forever without costing a lot and with minimal use. Although we hope it will take off.

I wanted to take some pain out of doing social sharing, so posting items automatically to twitter seemed like a good step to take. Of course, with my paradigm shift into Node.js and MongoDB, and it being fairly new across the board, I would have to write it myself, with the added benefit of being able to share how it was done!

Twitter's API documentation is pretty good, as far as API documentation goes. Their OAuth docs are on par with Google's and Facebook's (I use both of those on the site as well, as they are OAuth 2.0 and 2.0 is generally much easier than 1.0). All of their documentation can be found on dev.twitter.com, and that's also where you would create and manage your app, app keys and secrets, etc.

Data You'll Need

Now, completely ignoring the rate limiting part of the Twitter API, we can write a pretty straightforward method to send status updates as the user you created the application under. You can get the access tokens right in the Details section of your application. You'll need the access token and access token secret, as well as the consumer key and the consumer secret key. Don't give these to anyone!

Details Tab

The hardest part about OAuth 1.0 is generating the signature correctly. It's not that hard, either, since twitter provides excellent documentation on creating the signature. The base string used for the signature is created like this

The Code

These are the only requires you'll require (heh)

var https = require("https"), querystring = require("querystring"), crypto=require("crypto"); var oauthData = { oauth_consumer_key: consumerKey, oauth_nonce: nonce, oauth_signature_method: "HMAC-SHA1", oauth_timestamp: timestamp, oauth_token: accessToken, oauth_version: "1.0" }; var sigData = {}; for (var k in oauthData){ sigData[k] = oauthData[k]; } for (var k in data){ sigData[k] = data[k]; }

Here we're gathering up all of the data passed to create the tweet (status, lat, long, other parameters), and also the oauthData minus the signature. Then we do this:

var sig = generateOAuthSignature(url.method, "https://" + url.host + url.path, sigData); oauthData.oauth_signature = sig;

Which calls this function

function generateOAuthSignature(method, url, data){ var signingToken = urlEncode(consumerSecret) + "&" + urlEncode(accessSecret); var keys = []; for (var d in data){ keys.push(d); } keys.sort(); var output = "POST&" + urlEncode(url) + "&"; var params = ""; keys.forEach(function(k){ params += "&" + urlEncode(k) + "=" + urlEncode(data[k]); }); params = urlEncode(params.substring(1)); return hashString(signingToken, output+params, "base64"); } function hashString(key, str, encoding){ var hmac = crypto.createHmac("sha1", key); hmac.update(str); return hmac.digest(encoding); }

With this function, you can successfully generate the signature. The next part is passing the OAuth headers correctly. I simply do this:

var oauthHeader = ""; for (var k in oauthData){ oauthHeader += ", " + urlEncode(k) + "=\"" + urlEncode(oauthData[k]) + "\""; } oauthHeader = oauthHeader.substring(1);

And then create the request and pass it along on the request like this:

var req = https.request(url, function(resp){ resp.setEncoding("utf8"); var respData = ""; resp.on("data", function(data){ respData += data; }); resp.on("end", function(){ if (resp.statusCode != 200){ callback({error: resp.statusCode, message: respData }); } else callback(JSON.parse(respData)); }); }); req.setHeader("Authorization", "OAuth" + oauthHeader); req.write(querystring.stringify(data)); req.end();

Twitter Limits

There are other checks, like when you include a URL in the tweet text, you'll need to see that your text plus the length of the generated t.co URL doesn't exceed 140 characters. That URL length will change fairly infrequently, and slower and slower as time goes on, since more URLs can be generated with just 1 more character added. This data is available though. I have another function that gets the configuration from twitter, and passes that along to my function that actually generates tweets from the database.

function getHttpsNonAuthJSON(host, path, query, callback){ var url = { host: host , path: path }; if (query != null) url.path = url.path + "?" + querystring.stringify(query); https.get(url, function(resp){ resp.setEncoding("utf8"); var respData = ""; resp.on("data", function(data){ respData += data; }); resp.on("end", function(){ callback(JSON.parse(respData)); }); }); }

This is a general function to get any non-authenticated https request and perform a callback with a JS object. I might call it like this, for example

getHttpsNonAuthJSON("api.twitter.com", "/1/help/configuration.json", null, function(config){ console.log(config.short_url_length_http); });

For some completeness, this is the complete function that makes POST requests with OAuth

function postHttpsAuthJSON(host, path, data, nonce, callback){ var url = { host: host, path: path, method: "POST" }; var timestamp = Math.floor(new Date().getTime() / 1000); var oauthData = { oauth_consumer_key: consumerKey, oauth_nonce: nonce, oauth_signature_method: "HMAC-SHA1", oauth_timestamp: timestamp, oauth_token: accessToken, oauth_version: "1.0" }; var sigData = {}; for (var k in oauthData){ sigData[k] = oauthData[k]; } for (var k in data){ sigData[k] = data[k]; } var sig = generateOAuthSignature(url.method, "https://" + url.host + url.path, sigData); oauthData.oauth_signature = sig; var oauthHeader = ""; for (var k in oauthData){ oauthHeader += ", " + urlEncode(k) + "=\"" + urlEncode(oauthData[k]) + "\""; } oauthHeader = oauthHeader.substring(1); var req = https.request(url, function(resp){ resp.setEncoding("utf8"); var respData = ""; resp.on("data", function(data){ respData += data; }); resp.on("end", function(){ if (resp.statusCode != 200){ callback({error: resp.statusCode, message: respData }); } else callback(JSON.parse(respData)); }); }); req.setHeader("Authorization", "OAuth" + oauthHeader); req.write(querystring.stringify(data)); req.end(); }

Thanks for reading! I hope this helps you out. A reminder, this is just for posting as a single user, it doesn't go get the auth token or do any other of the handshaking that is needed for OAuth. Twitter generates an access token and a access secret token for use with the user that created the application, and you can do calls to the API with that, as that user.

My 1337th Tweet

Here it is. Awfully proud of that one