Easiest Way to Retrieve Twitter Timeline and Hashtags (Twitter OAuth API 1.1)

Written by Kevin Liew on 19 Mar 2013
218,297 Views • Tutorials

Introduction

Twitter terminated its old API, and all of my Twitter tutorials have stopped working! So, here is a follow up to show you how easy is it to retrieve user timeline and hashtag with Twitter REST API 1.1. Of course, I don't just stop there. I integrated my previously written script and modified it to work with Grid-A-Licious plugin to create something that's similar with Pinterest.

Before we start, we need to have all the ingredients ready.

  1. You need to create an application. https://dev.twitter.com/apps.
  2. Once you've done that, you need to get your consumer key, consumer secret, access token and access token secret.
  3. For security reason, Twitter OAuth is done through server side, in this tutorial, we're using PHP. To keep things simple, we are using Twitter Library called CodeBird-PHP.
  4. To make all the tweets we retrieve pretty, we will be using Grid-A-Licious

Once you have all that, we are good to good. This tutorial is a modified version of my previous old Twitter API - Create a Twitter Feed With Hash Tag And Cache Support. However, we won't implement cache control here just to make it easier to understand.

HTML

Alright, first stop. Since all HTML Markup will be generated by jQuery, we only need a div with an id called #jstwitter

<div id="jstwitter"></div>
<div class="item">
{IMG}
    <div class="tweet-wrapper">
        <span class="text">{TEXT}</span>
        <span class="time">
            <a href="{URL}" target="_blank">{AGO}</a>
        </span>
        by 
        <span class="user">{USER}</span>
    </div>
</div>

CSS

I keep CSS really simple, because in the end, I don't think you will be using mine, you probably will style it up according to your website design. So, I spice thing up a little bit. I'm going to create Pinterest style with Grid-A-Licious.

#jstwitter {
    position: relative;
}
#jstwitter .item {
    -webkit-border-radius:5px;
    -moz-border-radius:5px;
    border-radius:5px;
    -webkit-box-shadow:0 0 3px 1px rgba(100,100,100,0.2);
    -moz-box-shadow:0 0 3px 1px rgba(100,100,100,0.2);
    box-shadow:0 0 3px 1px rgba(100,100,100,0.2);
    overflow:hidden;
    background: #fff;
}
#jstwitter .tweet-wrapper {
    padding:10px;
    -webkit-box-sizing:border-box;
    -moz-box-sizing:border-box;
    box-sizing:border-box;
    line-height:16px;
}
#jstwitter .item a {
    text-decoration: none;
    color: #03a8e5;
}
#jstwitter .item img {
    width:100%;
}
#jstwitter .item a:hover {
    text-decoration: underline;
}
#jstwitter .item .text {
    display:block;
}
#jstwitter .item .time, #jstwitter .tweet .user {
    font-style: italic;
    color: #666666;    
}

PHP

I keep it really simple. We're not going to write our own PHP OAuth authentication, we will be using a Twitter Library called CodeBird-PHP. CodeBird makes it ridiculously easy to get authenticated. You may want to read more about it - CodeBird Documentation. As long as you get all the keys, tokens and secrets right, you should have any problems at all.

I have write inline comment in the following PHP script. If you want to add cache control to beat the rate limits in Twitter API. Here is the place you need to modify it.

<?
//We use already made Twitter OAuth library
//https://github.com/mynetx/codebird-php
require_once ('codebird.php');

//Twitter OAuth Settings
$CONSUMER_KEY = '...';
$CONSUMER_SECRET = '...';
$ACCESS_TOKEN = '...';
$ACCESS_TOKEN_SECRET = '...';

//Get authenticated
Codebird::setConsumerKey($CONSUMER_KEY, $CONSUMER_SECRET);
$cb = Codebird::getInstance();
$cb->setToken($ACCESS_TOKEN, $ACCESS_TOKEN_SECRET);

//retrieve posts
$q = $_POST['q'];
$count = $_POST['count'];
$api = $_POST['api'];

//https://dev.twitter.com/docs/api/1.1/get/statuses/user_timeline
//https://dev.twitter.com/docs/api/1.1/get/search/tweets
$params = array(
'screen_name' => $q,
'q' => $q,
'count' => $count
);

//Make the REST call
$data = (array) $cb->$api($params);

//Output result in JSON, getting it ready for jQuery to process
echo json_encode($data);
?>

Javascript / jQuery

Lastly, the Javascript. We called the PHP we just created. Depend on the parameters, I have made it so you can either search for hashtag, or load a user timeline. It send the parameters to the PHP file above, and PHP script get us authenticated, and PHP returns Twitter data in JSON format. The following scripts read the data and parse them in to HTML markup.

For more information about the Twitter Object, you can read it here.

$(function() {		
			
JQTWEET = {
     
    // Set twitter hash/user, number of tweets & id/class to append tweets
    // You need to clear tweet-date.txt before toggle between hash and user
    // for multiple hashtags, you can separate the hashtag with OR, eg:
    // hash: '%23jquery OR %23css'			    
    search: '%23heroes2013', //leave this blank if you want to show user's tweet
    user: 'quenesstestacc', //username
    numTweets: 21, //number of tweets
    appendTo: '#jstwitter',
    useGridalicious: true,
    template: '<div class="item">{IMG}<div class="tweet-wrapper"><span class="text">{TEXT}</span>\
               <span class="time"><a href="{URL}" target="_blank">{AGO}</a></span>\
               by <span class="user">{USER}</span></div></div>',
     
    // core function of jqtweet
    // https://dev.twitter.com/docs/using-search
    loadTweets: function() {

        var request;
         
        // different JSON request {hash|user}
        if (JQTWEET.search) {
          request = {
              q: JQTWEET.search,
              count: JQTWEET.numTweets,
              api: 'search_tweets'
          }
        } else {
          request = {
              q: JQTWEET.user,
              count: JQTWEET.numTweets,
              api: 'statuses_userTimeline'
          }
        }

        $.ajax({
            url: 'grabtweets.php',
            type: 'POST',
            dataType: 'json',
            data: request,
            success: function(data, textStatus, xhr) {
	            
	            if (data.httpstatus == 200) {
	            	if (JQTWEET.search) data = data.statuses;

                var text, name, img;	         
                	                
                try {
                  // append tweets into page
                  for (var i = 0; i < JQTWEET.numTweets; i++) {		
                  
                    img = '';
                    url = 'http://twitter.com/' + data[i].user.screen_name + '/status/' + data[i].id_str;
                    try {
                      if (data[i].entities['media']) {
                        img = '<a href="' + url + '" target="_blank"><img data-src="' + data[i].entities['media'][0].media_url + '" /></a>';
                      }
                    } catch (e) {  
                      //no media
                    }
                  
                    $(JQTWEET.appendTo).append( JQTWEET.template.replace('{TEXT}', JQTWEET.ify.clean(data[i].text) )
                        .replace('{USER}', data[i].user.screen_name)
                        .replace('{IMG}', img)                                
                        .replace('{AGO}', JQTWEET.timeAgo(data[i].created_at) )
                        .replace('{URL}', url )			                            
                        );
                  }
                
                } catch (e) {
                  //item is less than item count
                }
                
	             if (JQTWEET.useGridalicious) {                
	                //run grid-a-licious
			$(JQTWEET.appendTo).gridalicious({
				gutter: 13, 
				width: 200, 
				animate: true
			});	                   
		     }                  
                    
               } else alert('no data returned');
             
            }   
 
        });
 
    }, 
     
         
    /**
      * relative time calculator FROM TWITTER
      * @param {string} twitter date string returned from Twitter API
      * @return {string} relative time like "2 minutes ago"
      */
    timeAgo: function(dateString) {
        var rightNow = new Date();
        var then = new Date(dateString);
         
        if ($.browser.msie) {
            // IE can't parse these crazy Ruby dates
            then = Date.parse(dateString.replace(/( \+)/, ' UTC$1'));
        }
 
        var diff = rightNow - then;
 
        var second = 1000,
        minute = second * 60,
        hour = minute * 60,
        day = hour * 24,
        week = day * 7;
 
        if (isNaN(diff) || diff < 0) {
            return ""; // return blank string if unknown
        }
 
        if (diff < second * 2) {
            // within 2 seconds
            return "right now";
        }
 
        if (diff < minute) {
            return Math.floor(diff / second) + " seconds ago";
        }
 
        if (diff < minute * 2) {
            return "about 1 minute ago";
        }
 
        if (diff < hour) {
            return Math.floor(diff / minute) + " minutes ago";
        }
 
        if (diff < hour * 2) {
            return "about 1 hour ago";
        }
 
        if (diff < day) {
            return  Math.floor(diff / hour) + " hours ago";
        }
 
        if (diff > day && diff < day * 2) {
            return "yesterday";
        }
 
        if (diff < day * 365) {
            return Math.floor(diff / day) + " days ago";
        }
 
        else {
            return "over a year ago";
        }
    }, // timeAgo()
     
     
    /**
      * The Twitalinkahashifyer!
      * http://www.dustindiaz.com/basement/ify.html
      * Eg:
      * ify.clean('your tweet text');
      */
    ify:  {
      link: function(tweet) {
        return tweet.replace(/\b(((https*\:\/\/)|www\.)[^\"\']+?)(([!?,.\)]+)?(\s|$))/g, function(link, m1, m2, m3, m4) {
          var http = m2.match(/w/) ? 'http://' : '';
          return '<a class="twtr-hyperlink" target="_blank" href="' + http + m1 + '">' + ((m1.length > 25) ? m1.substr(0, 24) + '...' : m1) + '</a>' + m4;
        });
      },
 
      at: function(tweet) {
        return tweet.replace(/\B[@ï¼ ]([a-zA-Z0-9_]{1,20})/g, function(m, username) {
          return '<a target="_blank" class="twtr-atreply" href="http://twitter.com/intent/user?screen_name=' + username + '">@' + username + '</a>';
        });
      },
 
      list: function(tweet) {
        return tweet.replace(/\B[@ï¼ ]([a-zA-Z0-9_]{1,20}\/\w+)/g, function(m, userlist) {
          return '<a target="_blank" class="twtr-atreply" href="http://twitter.com/' + userlist + '">@' + userlist + '</a>';
        });
      },
 
      hash: function(tweet) {
        return tweet.replace(/(^|\s+)#(\w+)/gi, function(m, before, hash) {
          return before + '<a target="_blank" class="twtr-hashtag" href="http://twitter.com/search?q=%23' + hash + '">#' + hash + '</a>';
        });
      },
 
      clean: function(tweet) {
        return this.hash(this.at(this.list(this.link(tweet))));
      }
    } // ify
 
     
};		

});

And finally, you run the script like this:

$(function () {
    // start jqtweet!
    JQTWEET.loadTweets();
});

Conclusion

That's it my friend. If you have knowledge in both backend and frontend, web development can be easier. We can reuse a lot of scripts, for example, we use CodeBird, Gridalicious, my previous Twitter scripts. What we did right here, we use it, modify it, and bang, we have a working beautiful twitter feed interface.

I hope you will find this tutorial useful, if you have any question, don't hesitate to drop us a comment. :)

Demo Download
Join the discussion

Comments will be moderated and rel="nofollow" will be added to all links. You can wrap your coding with [code][/code] to make use of built-in syntax highlighter.

185 comments
Ray 11 years ago
Hello, this works great!

I have a very small question, can I make this autorefresh or autoscroll somehow? I have tried different scripts on it to no avail.

Thank you.
Reply
Olly 11 years ago
Is there a way of searching a users timeline just for images and displaying them?
Reply
Kevin Admin 11 years ago
You can put script 65-70 into line 60. Then, it will display image only.
Reply
Damian 9 years ago
Hey, can you explain this a little better as I have tried to get this to work following your comment but no luck... Thanks!
Reply
john gibbs 11 years ago
Hi, Thanks! Very useful script but ... I'm having a problem. i got the feed installed ok and am collecting tweets from a hashtag using the search api but none of the internal links in the tweets (i think the twitter shortened ones) are being rendered correctly. they are all coming out as http://t.… (a dud link)
i've checked thesame feed at twitter and all the links are OK there.
any ideas what's happening?
Reply
Mike 11 years ago
Then tutorial seemed very clear, but for some reason I can't get it to work. I created an app via twitter, and modified the following sections of the downloaded files:

(replaced actual content with XXX's)

user: 'XXXXX', //username

$CONSUMER_KEY = 'XXXX';
$CONSUMER_SECRET = 'XXXX';
$ACCESS_TOKEN = 'XXXX';
$ACCESS_TOKEN_SECRET = 'XXXX';

Do any other areas require editing for a basic version of the page to work?

I appreciate any guidance.

thanks,

Mike
Reply
Kevin Admin 11 years ago
That's all you need to do. Does it display any javascript error in the console? (You can use web developer tools builtin in Chrome or Safari)
Reply
Manthan Rana 11 years ago
Hi Kevin,

Great post. It was giving me error at first but fixed it.

Can I get tweets from different users and cache them & display them latest to older.

Can you point me in right direction so I can accomplish this?

Thanks,
Manthan
Reply
Kevin Admin 11 years ago
Hi Manthan, previously I wrote a twitter tutorial with cache support, that was based on old twitter API but the caching principle will be the same.

You can read it from here:
http://www.queness.com/post/10778/create-a-twitter-feed-with-hash-tag-and-cache-support
Reply
Manthan Rana 11 years ago
Hi,

Can we get Multiple user's Twitter feed with this?
Reply
john gibbs 11 years ago
hi Kevin,

one more question. would you know how to exclude retweets from the feed from the search api? i guess it could be achieved by adding another paramater to the params array. any ideas?

Thanks
Reply
webbasica 11 years ago
Sadly I'm getting the "no data returned" alert.
I'm leaving the search empty and using your test twitter account.

BTW, do you have a suggestion about how to use a image slider instead of the Grid-A-Licious?
Reply
Dave 11 years ago
Do you have an example on how to create a tweet list rather than using a grid page? sorta like a widget for a sidebar? do you know what I mean by that?
Reply
Kevin Liew Admin 11 years ago
Hi Dave, just remove all the CSS style, then you will have a list. You need to re-style it according to match your design.
Reply
danrose 11 years ago
Hi,

I think I have followed your tutorial in detail. But I keep on getting this:

Fatal error: Method name must be a string in /home/12345/public_html/queness/grabtweets.php on line 39

referring to:

$data = (array) $cb->$api($params);

I have to make this work...since I also need to be able to sendtweet (if you have a php on this as well.)

I'm not that good in php, just learning now. Ever since, this new twitter thing like they've made the whole thing so difficult!

Thanks!
Reply
nick 11 years ago
I'm having the same here. Have you already found a solution?
Reply
Matt 11 years ago
Having the same trouble as nick and danrose, did you guys find a solution please?
Reply
Will 11 years ago
Your timeAgo() function never does anything with the "week" variable. i.e. it will never say 4 weeks ago
Reply
Jeroen 11 years ago
I can't get it working :(
The only thing I'd see is the info bar on the top. (yellow background).

I'd not see any images or tweets appear. What could I do?
I changed in grabtweets.php:
$CONSUMER_KEY = 'XXX';
$CONSUMER_SECRET = 'XXX';
$ACCESS_TOKEN = 'XXXX';
$ACCESS_TOKEN_SECRET = 'XXX';

xxx = key

Nothing happends :(
Reply
Kevin Liew Admin 11 years ago
Any errors? You can use developer console to see the AJAX call. Does the php file generates any error messages?
Reply
Lennard van Diggelen 11 years ago
You are trying to change the timestamp by a condition to catch if the user is using a IE browser in the jquery.jstwitter.js file

   if ($.browser.msie) { 


$.browser is not supported in jQuery 1.9+ so there is need for a workaround. I did it with Modernizr but there are many more possibilities. If you are using 1.9+, the output will be blank.
Reply
anubhav 11 years ago
Is there any way to get the tweets date wise?
or
Is there any way to get more than recent 51 tweets?
Please help!!!
Reply
Kevin Liew Admin 11 years ago
I roughly remember that this API only return around 20 tweets. Something more than that, you will have to store all the tweet after retrieve it, and load the latest 51 from your database. It's a whole new level of complexity there because you need to schedule a cronjob to retrieve it automatically.
Reply