The Post Excerpt

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!

Resistance is futile.

©2023 ENGAGE APPS INC.