Monday, July 9, 2012

Create a Twitter Feed With Hash Tag And Cache Support

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.
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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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
    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">AGO</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) )
                        );                             
     
                    }                  
                 
                } 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?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['hours'] && $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.

17 comments:

Anonymous said...

hi just dropped by here pokie

layed out article, would love to see whatever you reveal following!
slots games

Anonymous said...

ola just dropped by the website netti casino

defined short article, would love to see that which you reveal next!
suomi casino

Anonymous said...

ola nicely done by the website ipad casino

given blog post, so want to notice that which you come up with next!
mobile casino

Anonymous said...

ola i came here by here online pokies

discussed blog post, so want to notice everything you come up with following!
online casino games

Anonymous said...

Hey there! I could have sworn I've been to this blog before but after browsing through some of the post I realized it's new to me.

Anyways, I'm definitely delighted I found it and I'll be
bookmarking and checking back often!
Also visit my blog post ; carpet cleaning service

Anonymous said...

I usually do not write many remarks, but i did a few searching and wound up here "Create a Twitter Feed With Hash Tag And Cache Support".
And I actually do have a few questions for you if you do not mind.
Is it simply me or does it give the impression like a few of these remarks appear as if they
are left by brain dead individuals? :-P And, if you are writing on other sites, I
would like to follow everything new you have to post.
Could you list of the complete urls of your shared sites like your linkedin profile, Facebook page or twitter feed?
Also visit my blog ; free laundry coupons

Anonymous said...

Hi there! I know this is kind of off topic but I was wondering which blog platform are
you using for this website? I'm getting tired of Wordpress because I've had issues
with hackers and I'm looking at options for another platform. I would be fantastic if you could point me in the direction of a good platform.

Also visit my web page brand of detergent

Anonymous said...

Spot on with this write-up, I truly think this amazing site
needs a lot more attention. I'll probably be returning to see more, thanks for the advice!

Stop by my page ... money on laundry detergent

Anonymous said...

What's Taking place i'm new to this, I stumbled upon this I've discovered It absolutely helpful and it has aided me out loads. I am hoping to contribute & help different users like its aided me. Great job.

My web blog ... cascade drive in

Anonymous said...

Spot on with this write-up, I really believe that this website
needs a lot more attention. I'll probably be returning to read more, thanks for the information!

Stop by my web site :: physician assistant jobs

Anonymous said...

Very good info. Lucky me I ran across your blog by chance (stumbleupon).

I've saved it for later!

Also visit my blog - tide laundry detergent

Anonymous said...

all the time i used to read smaller content which as well clear their motive, and that is also happening
with this paragraph which I am reading at this place.



Also visit my blog post ... little extra time

Anonymous said...

Hi! Would you mind if I share your blog with my myspace group?
There's a lot of people that I think would really appreciate your content. Please let me know. Many thanks

My web blog - brand of detergent

Anonymous said...

Everyone loves what you guys are usually up too. This kind
of clever work and reporting! Keep up the excellent works guys
I've included you guys to my blogroll.

Here is my website - laundry soap

Anonymous said...

Thanks a lot for sharing this with all folks you actually recognize what you're speaking approximately! Bookmarked. Please also visit my web site =). We could have a link change arrangement between us

Also visit my page: Extreme couponing

Anonymous said...

Thanks for one's marvelous posting! I quite enjoyed reading it, you are a great author. I will be sure to bookmark your blog and will often come back very soon. I want to encourage you continue your great posts, have a nice afternoon!

Feel free to surf to my homepage saving money on laundry

Anonymous said...

Hello! I could have sworn I've been to this website before but after reading through some of the post I realized it's new to me.

Anyhow, I'm definitely delighted I found it and I'll be book-marking and checking back frequently!


Also visit my web site :: top paid survey jewelery stores