Create a Twitter Feed With Hash Tag And Cache Support

Written by Kevin Liew on 20 Feb 2012
66,982 Views • Tutorials

Introduction

Due to popular demand, this tutorial is actually a revisit from my previous Twitter tutorial - Create a dead simple Twitter feed with jQuery. A lot of readers requested to be able to parse hashtag. So, I've decided to make a new version that able to do both plus some enhancements.

The foundation of this script will be the same, but with some modification to accept both hashtag and normal user feed. It will be smart enough to switch but with one tiny caveat which I will mention later on. You can see the preview or download it to play with the code.

UPDATE: We have written an updated version of Twitter API tutorial based on Twitter Newest API 1.1. - Easiest Way to Retrieve Twitter Timeline and Hashtags

Features

  • Able to parse User Timeline and hashtag (multiple hashtags by using OR operator to separate hashtags)
  • Cache result to avoid rate limits restriction from Twitter.
  • Decided to not using Cronjob to renew the cache, it uses date comparison with PHP.
  • Using links/hashtag/alias script to parse the tweet.

HTML

HTML is basically the same as the old one, nothing fancy.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
    <title></title>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js" type="text/javascript"></script>
    <script src="twitter.js"></script>
</head>
<body>
     
    <div id="jstwitter">
    </div>
     
</body>
</html>

CSS

CSS, nothing special, just simply style it up to make it presentable. However, this is a list of class that you can use to style it up:

  • .twtr-hashtag: #abc
  • .twtr-hyperlink: hyperlink
  • .twtr-atreply: #abc
  • .text: tweet
  • .user: username
  • .time: relative time (10 minutes ago)
body {
	background:#ccc;
}

#jstwitter {
	width: 300px;
	font-family: georgia;
	font-size: 12px;
	color: #333333;
	padding: 10px 10px 0 10px;
	margin:0 auto;
	background:#fff;
	border:5px solid #eaeaea;
	border-radius:15px;
	box-shadow:0 0 5px rgba(0, 0, 0, 0.3);
}

#jstwitter h1 {
	text-align:center;
	margin-top:5px;
}

#jstwitter .tweet {
	margin: 0 auto 15px auto;
	padding: 0 0 15px 0;
	border-bottom: 1px dotted #ccc;
}

#jstwitter .tweet a {
	text-decoration: none;
	color: #03a8e5;
}

#jstwitter .tweet a:hover {
	text-decoration: underline;
}

#jstwitter .tweet .text {
	display:block;
}

#jstwitter .tweet .time, #jstwitter .tweet .user {
	font-style: italic;
	color: #666666;
}

#jstwitter .tweet:last-child {
	border:0;
}

Javascript

Okay, the core. This jQuery script added a few if-else statements to toggle between hashtag and user timeline. I put inline comment in every crucial code for better understanding of what it does.

The way it works:

  1. Firstly, you can set either hashtag or username. If you filled in hashtag, username will be ignore. If you want username, you need to leave hashtag empty.
  2. You can set other setting such as number of tweet, cache expiry date, append to element.
  3. In loadTweet function, it constructs different request and pass it to PHP script, and then PHP script will format the request with the right Twitter API and grab the content. The PHP script is responsible to handle the cache as well. Depend on how long you set in the cacheExpiry, it will renew the cache accordingly.
  4. Finally, the Javascript will parse the returned JSON based on the type of API. (hashtag and user timeline's JSON is a bit different in structure)

Some tips and tricks

Multiple Hashtags: You can separate the hastags with OR operator. eg: '%23jquery OR %23css'

Multiple Users: You can use 'from:account' and separate with OR operator. eg: 'from:quenesswebblog OR from:favbulous'

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'
	hash: '%23jquery', //leave this blank if you want to show user's tweet
	user: 'quenesswebblog', //username
	numTweets: 5, //number of tweets
	cacheExpiry: 2, //get the new cache in 2 hours
	appendTo: '#jstwitter',
	
	// core function of jqtweet
	// https://dev.twitter.com/docs/using-search
	loadTweets: function() {
	
		var request;
		
		// different JSON request {hash|user}
		if (JQTWEET.hash) {
			request = {
				q: JQTWEET.hash,
				expiry: JQTWEET.cacheExpiry,				
				api: 'http://search.twitter.com/search.json'
			}
		} else {
			request = {
				screen_name: JQTWEET.user,
				include_rts: true,
				count: JQTWEET.numTweets,
				include_entities: true,
				expiry: JQTWEET.cacheExpiry, 
				api: 'http://api.twitter.com/1/statuses/user_timeline.json/'
			}
		}

		$.ajax({
			url: 'tweets-grab.php',
			type: 'GET',
			dataType: 'json',
			data: request,
			success: function(data, textStatus, xhr) {

				var text, name, html = '<div class="tweet"><span class="text">TWEET_TEXT</span><span class="time"><a href="URL" target="_blank">AGO</a></span> by <span class="user">USER</span></div>';
		
				try {
		
					//Twitter Search API has different JSON Structure
					if (JQTWEET.hash) data = data['results'];
		
					// append tweets into page
					for (var i = 0; i < data.length && i < JQTWEET.numTweets; i++) {
						
						name = (JQTWEET.hash) ? data[i].from_user : data[i].user.screen_name;
	
						$(JQTWEET.appendTo).append(	
						    html.replace('TWEET_TEXT', JQTWEET.ify.clean(data[i].text) )
						        .replace(/USER/g, name)
						        .replace('AGO', JQTWEET.timeAgo(data[i].created_at) )
						        .replace('URL', 'http://twitter.com/' + data[i].from_user + '/status/' + data[i].id_str )
						);								
	
					}					
				
				} catch (e) {
					alert('No data returned, you might want to clear tweets-date.txt.');
				}			
			
			}	

		});

	}, 
	
		
	/**
      * 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

	
};



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

PHP

Right, the final part of this tutorial - the PHP. It:

  • Checks for Cache Expiry interval
  • Constructs the API call
  • Retrieves JSON data from Twitter
  • Saves both JSON data and date

We need two text files:

  • tweets-cache.txt: contains latest data in JSON format
  • tweets-date.txt: contains the date of last retrieval

In the introduction, I mentioned about a small caveat of this script - Whenever you switch between hashtag and username, you need to clear the tweets-date.txt.

<?php


$cache = 'tweets-cache.txt';
$date = 'tweets-date.txt';

$currentTime = time(); // Current time

// Get cache time
$datefile = fopen($date, 'r');
$cacheDate = fgets($datefile);
fclose($datefile);


//check if cache has expired
if (floor(abs(($currentTime-$cacheDate) / 3600)) <= $_GET['expiry'] && $cacheDate) {

	$cachefile = fopen($cache, 'r');
	$data = fgets($cachefile);
	fclose($cachefile);

} else { //renew the cache

	//toggle between API
	if ($_GET['q']) 
	{
		$data = file_get_contents($_GET['api'] . '?q=' . urlencode($_GET['q']));   
	
	} else if ($_GET['screen_name']) 
	{
		$data = file_get_contents($_GET['api'] . '?screen_name=' . $_GET['screen_name'] . '&count=' . $_GET['count'] . '&include_rts=' . $_GET['include_rts'] . '&include_entities=' . $_GET['include_entities']);   
	}
	
	// update cache file
	$cachefile = fopen($cache, 'wb');  
	fwrite($cachefile,utf8_encode($data));  
	fclose($cachefile); 

	// update date file
	$datefile = fopen($date, 'wb');  
	fwrite($datefile, utf8_encode(time()));  
	fclose($datefile); 	 
}


header('Content-type: application/json');
echo $data;
?>

Conclusion

So, that's how you do it, support both hashtag and username, easy to style and uses cache to overcome Twitter rate limits. You just have to remember, whenever you switch between hashtag and username, remember to clear the content of tweets-date.txt.

That's it, hope you will find this useful and any questions, just drop me a comment and I will try my very best to answer it quickly. Stay tuned with queness! For more cool and updated javascript inspiration, tutorials, plugins, you can follow us on twitter or like us on facebook.

Demo 1Demo 2Download
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.

71 comments
Andre 12 years ago
This script looks really good. I'm going to look at implementing this on my site. Our server is one hour behind local time at the moment, as it does not change to daylight saving time (DST). Any advice on how to work around this?
Reply
John 12 years ago
I'm not sure if it's just me, but my feed that was working perfectly over the past few months has suddenly stopped working. It continually wants to empty the tweets-cache.txt file. Am I the only one?
Reply
Dan Baker 11 years ago
Hey Great Tutorial, a feed that actually works after the changes to the API! could this be adapted to display one tweet at a time and fade in and out to the next...
Reply
Luis Carlos Laguna 11 years ago
hey!!! i optimized your code with this

$fp = fopen('tweets-date.txt', 'w');
fwrite($fp, '');
fclose($fp);

put this on teets-grabs! , with this code you can get the last hashtag tweet... when you refresh your browser :D, without this code, it's so hard to get the last one!
Reply
tavax 11 years ago
Great script, however maybe it's just me but I can't load my account, if I remplace the hash by blank or by my nickname and if I add too or not in the username, nothing appear! I don't understand why
Reply
Barry 11 years ago
Thx for a nice script. If I call it directly it works. But if I add it to a WordPress theme header, nothing shows up, and the error log says:

PHP Warning: file_get_contents
failed to open stream: HTTP request failed! HTTP/1.0 400 Bad Request
from jstwitterhash/tweets-grab.php on line 32

allow_url_fopen is ON

Any thoughts?
Reply
Fabio 11 years ago
Hi Kewin, this is exactly the script I was looking for. Unfortunately, I'm not so familiar with this kind of application, so I have a "basic" question: could I use it in Blogspot ? where should I load it ? I'd like to have it as an header in one of the pages or in the main page of my Blogger blog. Thanks in advance
Reply
Michael 11 years ago
Hi!
Is it possible to make the time stamp info to be a link to the tweet at twitter.com? Like on twitter.com.
Reply
Kevin Liew Admin 11 years ago
Yep, updated the post.
Reply
Shumail 11 years ago
Hi, Thanks for the awesome script !

Can you please tell how can i make it to update in realtime so that it automatically update the tweets without refreshing the page ? and override the maximum limit of tweets that is 5 and show many tweets with a scroll bar ???

Please help...
Reply
Justin 11 years ago
Does this script have a PHP version requirement? I'm getting a 403 Forbidden error on a server with v5.2. allow_url_fopen is turned on. Any reason that would happen?
Reply
Kevin Liew Admin 11 years ago
hmm, it shouldn't have any requirements. The PHP script is pretty basic.
Reply
Michael 11 years ago
Hi! How do I make it work with Twitter API 1.1?
Thanks in advance.
Reply
Michael 11 years ago
Hi!
Just to let you know that jQuery.browser is deprecated.

http://blog.jquery.com/2013/01/15/jquery-1-9-final-jquery-2-0-beta-migrate-final-released/

From http://blog.jquery.com/2013/01/15/jquery-1-9-final-jquery-2-0-beta-migrate-final-released/

JQMIGRATE: jQuery.browser is deprecated

Cause: jQuery.browser was deprecated in version 1.3, and finally removed in 1.9. Browser sniffing is notoriously unreliable as means of detecting whether to implement particular features.

Solution: Where possible, use feature detection to make code decisions rather than trying to detect a specific browser. The Modernizr library provides a wide variety of feature detections. As a last resort, you can directly look at the navigator.userAgent string to detect specific strings returned by the browser.
Reply
Dedoceo 11 years ago
Hi, Thanks for all the work and information here. I've been trying to add this functionality to a site I'm working on but I do not see the Twitter information in any environment. I was able to see it using the initial option but since I've changed to avoid the hit counter problem I am going nowhere... Here's the only change I made to the .js file: 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
hash: '', //leave this blank if you want to show user's tweet
user: 'CasesTheSeries',
//from:'CasesTheSeries OR Dedoceo',
numTweets: 6,
cacheExpiry: 1, //get the new cache in hours
appendTo: '#jstwitter',

Both of the Text files are uploaded locally and on my server (I did try to get the date to update and it did not). I would like to know if someone can help me resolve this and am happy to provide whatever information is needed.
Reply