Posting to Twitter with Node.js

February 3, 2012
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.

Comments

Photography Lighting

January 9, 2012

In recent weeks, I've become interested in going to the next level of photography. Not to say that I've mastered any previous part, but going to the next level will help me better understand the previous levels, and therefore I'll be half decent at any part. Lighting is the next level, I've determined.

In researching "The Holy Grail of Photography", henceforth, "lighting", I came across some blogs of old pros, namely the "Strobist". There, I read and read and read until I fell asleep. I bought a few Kindle prints of Joe McNally's books. I read and read and read, then did some practicing.

This is all in anticipation of the baby coming in April. I will blind that kid :P

Aside from books, here's what I've purchased:

  • Nikon SB-700 flash
  • CowboyStudios wireless flash trigger
  • CowboyStudios light stand with mounting bracket and umbrella
  • LumiQuest mini-softbox

I haven't been uploading sample shots with every piece of equipment yet, but the pictures up right now (as of 1/9/2012) include sample shots taken with the flash and the mini-softbox. I have shots with the wireless flash trigger that aren't up on flickr yet, and I just received the stand and umbrella today, and I haven't gone home yet.

I've been getting up to date on all of the terminology used. I know what effects the aperture has vs. shutter speed when flash comes into play. With flash and without flash, actually. And ISO plays a big part in that as well.

I shoot in manual mode on my camera without a flash, and so I've brought that over to flash photography as well. My method now as always is shoot and review to see if I'm getting enough light. If not, bump the flash power or open/close the aperture, etc. Or refocus, since I also shoot in manual focus mode. With practice, I'm getting quicker at it..

Most photogs suggest shooting in aperture priority mode, since aperture is what effects flash the most. If you want to darken the flash, close the aperture, and vice versa. In non-flash photography, it also makes sense, because you can limit the depth of field (make the background blurry) or go infinite on that mother, and the camera will choose the proper shutter speed. I have opted to really learn everything before I go that way. The side effect is I take about 1 good photo for every 5 times my shutter opens :)

A good side effect of all of this photography, is I'm becoming a photo snob! If I look at a bad photo, either blurry or not enough light, or too much light, I get an ill feeling down in my bowels and have to just close my eyes or look away. It's good to have.

Lots of photos to come, especially when there's a baby AND a pug in the house :D

Comments

50mm f/1.8 Lens

December 27, 2011

For Christmas, I got a new lens! As well as some other awesome stuff, but the lens is the focal point of this post.

I'm generally a beginner photographer, but over the past few weeks I've been taking hundreds of pictures, I got a new flash (the SB-700) and have been playing with that. I want to try getting the flash off the camera, and there's tons of little accessories I don't own yet that will help.

Here is a sample picture of what I've been able to do with the f/1.8 lens. It's pretty awesome so far! That's my nephews, with Ethan in the foreground and Caden in the background, both playing their new Nintendo 3DS's that they got for Christmas :)

DSC_4768

My other favorite photo with the new lens, Amanda and Beaker, enjoying the Snoogle that I got for Amanda for Christmas!

Enjoying the Snoogle
Comments

Photographer's Blessing

December 21, 2011
May your aperture be filled with light, but not too much!
Comments

Another Baby Appointment

December 20, 2011
Amanda and I had to go back to the hospital for more ultrasound pictures, since the baby wasn't cooperating when we tried to get pictures of the heart!  So we saw another light show, and the baby headbanging. It's got some Nirvana or something in there. Rock on baby.
Tags baby
Comments

Baby

December 13, 2011

The baby is growing!

Baby at 20 Weeks

The baby is 10 oz and about the length of a banana! We aren't finding out the sex so I don't want to say "He/She" so I'll just refer to the baby as "the baby".

Comments

Friday is just around the corner

November 17, 2011
I made this.

Friday
Tags funny friday
Comments

Humility

November 15, 2011
The ability to lie about how awesome I am.
Tags words funny
Comments

Another Example of my SyncArray

November 2, 2011

I refer you to my original post with my SyncArray code

function getSubdirs = function(path, callback){ fs.readdir(path, function(err, files){ var sync = new SyncArray(files); var subdirs = []; sync.forEach(function(file, index, array, finishedOne){ fs.stat(file, function(err, stats){ if (stats.isDirectory()){ subdirs.push(file); } finishedOne(); }); }, function(){ callback(subdirs); }); }); }
Comments

Messing Around in JS Object - Prevent Extensions

October 25, 2011

Lately I've been messing around with Javascript Object in Javascript 1.8.5 or greater. It's interesting but I've come across some peculiarities.

Object.preventExtensions tests

function d(){ this._test = 5; } //Object.preventExtensions(d.prototype); if (Object.isExtensible(d.prototype)){ Object.defineProperty(d.prototype, "test", { get: function(){ console.log("get called"); return this._test; }, enumerable: false, configurable: true } ); } var s = new d(); Object.preventExtensions(s); s.test = 6; console.log(s.test); s.jason = "jason"; console.log(s.jason);

Calling preventExtensions on d.prototype, the "test" property is never defined with the getter, so console.log(s.test) won't also log "get called". Object.preventExtensions(d.prototype) does not prevent extensions on instances of d. This one was weird to me, but they are two separate objects, so creating the property "jason" on "s" works unless I prevent extensions on "s". When preventExtensions is called on "s", console.log(s.test) prints 5 and console.log(s.jason) does not error out although it raises an error if I try to extend the prototype when it is not extensible.

Just some observations... This is the first in a series of posts on Object and its new methods in 1.8.5, I feel I need to know everything about it

Comments