Recently, I had to find a solution to query Twitter’s Search API with my Ionic and Cordova app.
I had found some great posts by @nraboy and @saimon24 on very similar topics, so I expanded on them and I specifically modified the code found in saimon24’s post: http://blog.ionic.io/displaying-the-twitter-feed-within-your-ionic-app/
His code allows you to query Twitter’s API to return a timeline, with this link: https://api.twitter.com/1.1/statuses/home_timeline.json. I think his code will work for any of the static end points, but it doesn’t allow you to query the search API with additional parameters, such as this link:
https://api.twitter.com/1.1/search/tweets.json?q=yoga&result_type=recent
Notice the two extra parameters: q & result_type. The search api accepts a few other parameters, and the whole trick to it is in creating a signature hash that includes all params, including the oAuth parameters generated by ngCordova’s createSignature function.
Otherwise you’ll get this error:
{"errors":[{"code":32,"message":"Could not authenticate you."}]}
The end goal is to produce a url string that looks something like this, with the right signature!:
https://api.twitter.com/1.1/search/tweets.json?q=yoga&result_type=recent&oauth_consumer_key=7MrHtnETWM57mF6lKHskujPe7&oauth_token=583492865-A0EakIORoKNv7wYPIwwKNahO2tTOXmnuAH6Wq7uv&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1435852245&oauth_nonce=75Mk8T&oauth_version=1.0&oauth_signature=qq5z1PiiXB6u7pcGBCkblnpBBw%3D
Notice that it includes the two twitter API parameters, along with 6 oAuth parameters.
So let’s say you’re building an angular service that queries Twitter’s API and stores the resulting tweets. You’ll need to include $q, $cordovaOauth and $cordovaOauthUtility as dependencies and set up the following code:
if (window.cordova) { var q = $q.defer(); //This could be any API Endpoint on Twitter that accepts params var search_tweets_url = 'https://api.twitter.com/1.1/search/tweets.json'; //Use saimon's createTwitterSignature Function var oauthObj = createTwitterSignature('GET', search_tweets_url, buildParamObj(keywordObj)); //Save and Delete the oAuth signature object because $.param screws it up var sigTemp = oauthObj.oauth_signature; delete oauthObj.oauth_signature; //Convert OAuth object to params, adjust for signature //Using $http because $resource gives me config errors for action query. Callback instead of promise :( //Make sure to include the $q library $http.get( search_tweets_url +'?' + buildParamString(keywordObj) + '&' + $.param(oauthObj) + '&oauth_signature=' + sigTemp ) .success(function(data, status, headers, config) { console.log('Successul HTTP Request', data); tweets = data.statuses; q.resolve(tweets); }) .error(function(data, status, headers, config) { console.log('Rejected HTTP Request ', status, headers); q.reject(reject); }); return q.promise; }
To start you’ll some sort of object with all your search parameters. I’ve only included 3 of them here, but there are several more you can use.
{ q: 'Yoga', result_type: 'recent', count: 50 }
You can create the object above from another object (keywordFormObj) generated by your own custom input form (not covered in this post):
function buildParamObj(keywordFormObj) { var paramObject = {}; paramObject.q = keywordFormObj.query; paramObject.result_type = "recent"; if (keywordFormObj.lang) { paramObject.lang = keywordFormObj.lang; } //Count paramObject.count = 50; return paramObject; }
Now this new object has to be sent into the createTwitterSignature function as a third parameter, like so:
var oauthObj = createTwitterSignature('GET', search_tweets_url, buildParamObj(keywordObj));
And here is saimon’s slightly modified createTwitterSignature function (that still works with his prior post/code)
function createTwitterSignature(method, url, paramObj) { if (!paramObj) { paramObj = {}; } var oauthObject = { oauth_consumer_key: clientId, oauth_nonce: $cordovaOauthUtility.createNonce(10), oauth_signature_method: "HMAC-SHA1", oauth_token: accessToken, oauth_timestamp: Math.round((new Date()).getTime() / 1000.0), oauth_version: "1.0" }; var signatureObj = $cordovaOauthUtility.createSignature(method, url, oauthObject, paramObj, clientSecret, accessTokenSecret); $http.defaults.headers.common.Authorization = signatureObj.authorization_header; console.log('created Headers', $http.defaults.headers, signatureObj); return oauthObject; }
That function will return an oAuthObject with all the extra params you need to build your final search URL for twitter.
I then use jQuery to turn that object into a param string using $.param()
But there’s a slight problem because that function will re-encode any % chars, ruining the oauth_signature property, so I temporarily save the oauth_signature property from the object, delete it, and go ahead and create the parameter string. I then reattach the oauth_signature to the end of it.
search_tweets_url +'?' + $.param(buildParamObj(keywordObj)) + '&' + $.param(oauthObj) + '&oauth_signature=' + sigTemp
Notice how I also have to convert the buildParamObj into a string as well and attach it to the url. Be careful though, if you have special characters in your object, jquery might re-encode them, so you might want to build your param string manually, without using $.param().
$http is another dependency that you have to add to your service. In saimon24’s post he uses $resource, however I found it troublesome with complex queries, and decided to switch to $http. Instead of using promises it uses callbacks, so I wrapped the whole thing in a $q promise and I only return the result when the callback is completed, forcing my program to wait for the result from the service.
And here’s a bit of extra code that returns the same data, however I use it when I’m testing in the browser where ngCordova doesn’t work. You’ll need to include CodeBird to your project though!
if (!window.cordova) { var cb = new Codebird; cb.setConsumerKey(clientId, clientSecret); cb.setToken(accessToken, accessTokenSecret); cb.__call( "search_tweets", buildParamString(keywordObj), function(reply, rate_limit_status) { console.log('Codebird ', reply, rate_limit_status); tweets = reply.statuses; q.resolve(tweets); } ); }
Good luck!