Adding a “Latest Tweet” widget to your site

Let’s face it: where Ben Nadel goes, many will follow. Including me. When he redesigned his site last weekend, he made his latest Twitter tweet a prominent feature above the fold. I thought that was a good idea, so I set about doing it for mine. Here’s a quick and simple way to do it that’ll take you less than 15 minutes.

I’ve done mine with an AJAX gateway so that it is added asynchronously to the page after it finishes loading. However, you’ll see that it would be easy enough to include inline using the same framework. You want to use a gateway not just because of cross-site AJAX issues, but also because you want to rate-limit how often you hit the Twitter API.

The gateway does two things: (1) fetch and cache your latest tweets, and (2) filter them to exclude tweets that wouldn’t make sense on your site. Here’s a very simplified version of the gateway code:

<cfsetting enablecfoutputonly="Yes">
<!---
  twitter-latest.cfm
--->
<cfcontent type="application/xml" reset="true">
<cfset username     = "yourusername">
<cfset password     = "yourpassword">
<cfset agent        = "youremail">
<cfset apiUrl       = "http://twitter.com/statuses/user_timeline.xml?screen_name=" & username>
<cfset timeoutHours = 0.25>
<cfoutput><?xml version="1.0" encoding="UTF-8"?>
</cfoutput>
<cflock name="twitter-latest" type="EXCLUSIVE" timeout="5">
  <cfscript>
  if(structKeyExists(application, "twitter-latest")) twit = application["twitter-latest"];
  else {
    twit = structNew();
    twit.lastUpdate    = createDate(1970,1,1);
    twit.lastTweet     = "";
    twit.lastTweetDate = "";
    twit.lastTweetID   = "";
    application["twitter-latest"] = twit;
  }
  needUpdate = true;
  if(structKeyExists(twit, "lastUpdate") and isDate(twit.lastUpdate) and (dateAdd("h", timeoutHours, twit.lastUpdate) gte now()) and structKeyExists(twit, "lastTweet") and isSimpleValue(twit.lastTweet) and (twit.lastTweet neq ""))
    needUpdate = false;
  </cfscript>
  <cfif needUpdate>
    <cftry>
      <cfhttp method="GET" url="#apiUrl#" useragent="#agent#" username="#username#" password="#password#"></cfhttp>
      <cfscript>
      if (cfhttp.statusCode contains "200") {
        tweets = xmlParse(cfhttp.fileContent);
        statuses = xmlSearch(tweets, "/statuses/status");
        if (isArray(statuses)) {
          for(i = arrayLen(statuses); i gte 1; i = i - 1) {
            status = statuses[i];
            if (not (structKeyExists(status, "in_reply_to_user_id") and isNumeric(status["in_reply_to_user_id"].xmlText))) {
              twit.lastTweet     = xmlFormat(status.text.xmlText);
              twit.lastTweetID   = xmlFormat(status.id.xmlText);
              twit.lastTweetDate = xmlFormat(status.created_at.xmlText);
              twit.lastUpdate    = now();
            }
          } // for i
        } // if statuses
      } // if 200
      </cfscript>
    <cfcatch></cfcatch>
    </cftry>
  </cfif>
</cflock>
<cfif structKeyExists(twit, "lastTweetDate") and structKeyExists(twit, "lastTweetID") and structKeyExists(twit, "lastTweet")>
  <cfoutput><p twitterdate="#twit.lastTweetDate#" twitterid="#twit.lastTweetID#">#twit.lastTweet#</p></cfoutput>
<cfelse>
  <cfoutput><p/></cfoutput>
</cfif>
<cfsetting enablecfoutputonly="No">

I’ve single-threaded the bulk of the code, as well as done something that many consider evil: a try/catch without any error-handling. However, what useful thing would you do if you encountered an error?

My filter logic is straightforward: don’t show replies. If I’m replying to someone, it probably wouldn’t mean anything to a random visitor on my website, so why bother showing it?

The AJAX code to add this to your site is even simpler. I used jQuery, as I’ve already got it in place on my site, but the vanilla JavaScript version wouldn’t be much longer.

<script language="JavaScript" defer="defer" type="text/javascript">
jQuery(document).ready(function(){
  jQuery.get('/ajax/twitter-latest.cfm', {}, function(d){
    var tb = jQuery('#twitter-body');
    if(tb.length == 0) return;
    var tweet = d.firstChild.textContent;
    tweet = tweet.replace(/(https?[:]\/\/[-A-Z0-9a-z./]+)/g, '<a href="$1" rel="nofollow">$1</a>');
    tweet = tweet.replace(/@([_A-Z0-9a-z]+)/g, '<a href="http://twitter.com/$1" rel="nofollow">@$1</a>');
    tweet = tweet.replace(/#([_A-Z0-9a-z]+)/g, '</a>a href="http://search.twitter.com/search?q=%23$1" rel="nofollow">#$1</a>');
    tb.html(tweet);
  }, 'xml');
});
</script>

The replace() regular expressions do a passable job of auto-linking URLs, Twitter user names, and hash tags. The URL regex is especially fragile, but since most URLs posted on Twitter are shortened, it’s good enough. You could add in more error-checking logic for when Twitter goes down, maybe hiding the Twitter box altogether in such cases.

Published by

Rick Osborne

I am a web geek who has been doing this sort of thing entirely too long. I rant, I muse, I whine. That is, I am not at all atypical for my breed.