Skip to content

Instantly share code, notes, and snippets.

@joemccann
Created August 29, 2012 14:11
Show Gist options
  • Save joemccann/3513142 to your computer and use it in GitHub Desktop.
Save joemccann/3513142 to your computer and use it in GitHub Desktop.
node twitter oauth hell.
// This is in the same file in my node app, but split so you can see what is being used where
pipeToTwitter: function(echo, req, res){
// TODO: Not sure if this check goes here or in pipePhotoToTwiter() in twitter.js plugin
if(!req.session.twitter.oauth){
res.type('text/plain')
return res.status(403).send("You are not authenticated with Facebook.")
}
// TODO: EVENTUALLY WE WILL NEED TO CHECK THE
// https://api.twitter.com/1/help/configuration.json
// RESPONSE THAT CONTAINS SHORT URL CHARS AND MAX MEDIA UPLOADS
// SEE https://dev.twitter.com/docs/api/1/get/help/configuration
var oauth = req.session.twitter.oauth
var uri = 'https://upload.twitter.com/1/statuses/update_with_media.json'
var method = 'POST'
var authHeaders = createAuthHeaders(oauth, uri, method )
console.dir(echo)
console.dir(authHeaders)
var command = 'curl --request \'POST\' \'https://upload.twitter.com/1/statuses/update_with_media.json\' '+
'--header \''+authHeaders+'\' -F "media[]=@'+echo.fullPhotoPath+'" -F "status='+echo.caption+'" --header "Expect: "'
console.log('\n\n'+command+'\n\n')
exec(command, function(err,data){
if(err) {
console.error(err)
return res.json(err)
}
if(data) {
console.dir(data,8)
// NOTE: If the user tries to exceed the number of updates allowed,
// this method will also return an HTTP 403 error, similar to POST statuses/update.
// TODO: CHECK FOR THIS!!
return res.json(JSON.parse(data))
}
}) // end exec()
}
var fs = require('fs')
, path = require('path')
, request = require('request')
, qs = require('querystring')
, exec = require('child_process').exec
, crypto = require('crypto')
var twitter_config = JSON.parse( fs.readFileSync( path.resolve(__dirname, 'twitter-config.json'), 'utf-8' ) )
function uuid(){
var s = [], itoh = '0123456789ABCDEF';
// Make array of random hex digits. The UUID only has 32 digits in it, but we
// allocate an extra items to make room for the '-'s we'll be inserting.
for (var i = 0; i <36; i++) s[i] = Math.floor(Math.random()*0x10);
// Conform to RFC-4122, section 4.4
s[14] = 4; // Set 4 high bits of time_high field to version
s[19] = (s[19] & 0x3) | 0x8; // Specify 2 high bits of clock sequence
// Convert to hex chars
for (var i = 0; i <36; i++) s[i] = itoh[s[i]];
// Insert '-'s
s[8] = s[13] = s[18] = s[23] = '-';
return s.join('');
}
function sha1 (key, body) {
return crypto.createHmac('sha1', key).update(body).digest('base64')
}
function rfc3986 (str) {
return encodeURIComponent(str)
.replace(/!/g,'%21')
.replace(/\*/g,'%2A')
.replace(/\(/g,'%28')
.replace(/\)/g,'%29')
.replace(/'/g,'%27')
;
}
function hmacsign (httpMethod, base_uri, params, consumer_secret, token_secret, body) {
// adapted from https://dev.twitter.com/docs/auth/oauth
var base =
(httpMethod || 'GET') + "&" +
encodeURIComponent( base_uri ) + "&" +
Object.keys(params).sort().map(function (i) {
// big WTF here with the escape + encoding but it's what twitter wants
return escape(rfc3986(i)) + "%3D" + escape(rfc3986(params[i]))
}).join("%26")
var key = encodeURIComponent(consumer_secret) + '&'
if (token_secret) key += encodeURIComponent(token_secret)
return sha1(key, base)
}
function createAuthHeaders(_oauth, uri, method){
var form = {}
var oa = {}
for (var i in form) oa[i] = form[i]
for (var i in _oauth) oa['oauth_'+i] = _oauth[i]
if (!oa.oauth_version) oa.oauth_version = '1.0'
if (!oa.oauth_timestamp) oa.oauth_timestamp = Math.floor( (new Date()).getTime() / 1000 ).toString()
if (!oa.oauth_nonce) oa.oauth_nonce = uuid().replace(/-/g, '')
oa.oauth_signature_method = 'HMAC-SHA1'
var consumer_secret = oa.oauth_consumer_secret
delete oa.oauth_consumer_secret
var token_secret = oa.oauth_token_secret
delete oa.oauth_token_secret
var baseurl = uri.protocol + '//' + uri.host + uri.pathname
var signature = hmacsign(method, baseurl, oa, consumer_secret, token_secret)
// oa.oauth_signature = signature
// Not used?
for (var i in form) {
console.log('i in form '+ i)
if ( i.slice(0, 'oauth_') in _oauth) {
// skip
console.log('skipping')
} else {
delete oa['oauth_'+i]
}
}
// NOTE: I added a space after the commma in the join() to explicitly match the way Twitter is doing it
var authorization =
'Authorization: OAuth '+Object.keys(oa).sort().map(function (i) {return i+'="'+rfc3986(oa[i])+'"'}).join(', ')
authorization += ', oauth_signature="'+rfc3986(signature)+'"'
return authorization
}
curl --request 'POST' 'https://upload.twitter.com/1/statuses/update_with_media.json' --header 'Authorization: OAuth oauth_consumer_key="Q3WlVw5BskIdGouOEZEv2w", oauth_nonce="c6f366d2c9e7b2669fd1d5ece56f61d7", oauth_signature="Ba6IB8uH2SjtrK8a%2FgZnqCgvIKs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1346207448", oauth_token="14814762-vvYtBOLX8hBAQ0i0f1k4wxrioG1jOk49MJrqn3myE", oauth_version="1.0"' -F "media[][email protected]" -F "status=Test from cURL" --header "Expect: "
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment