Christian Heilmann

Posts Tagged ‘twitter’

Useful tweets widget using Yahoo Pipes and some JavaScript

Sunday, September 28th, 2008

If you look on the right side of this blog (and you can see) and you have JavaScript enabled you’ll spy a little “Useful tweets” widget (list). This is done with Yahoo Pipes and some JavaScript. As people asked me how it is done, here goes:

The idea

I use twitter a lot. Some of what I write is very relevant to the blog here, some is not fit for publication and some is just personal. So publishing all the tweets here would have been disruptive, hence I tried to find a way to filter things down.

What I do is that I end every tweet that I want to show up here with a § symbol, thus giving me a handle to filter out the good ones.

Playing nice with twitter and not summoning the fail whale

As twitter is probably the most hit API out there I didn’t want to go through the API and all the authentication malarkey. Instead I am using the ATOM feed and pipes to get the information and to filter it down.

Yahoo pipes is still full of win when it comes to filtering, mashing and converting data, and the pipe in question that I am using is available here: Useful tweets pipe

It takes the atom feed of a twitter user of a certain ID, removes all tweets but the ones ending in a § and removes the user name of the output.

Using the pipe and displaying the content

In order to display the pipe all you need is a small JavaScript and the right HTML in your page (or in my case WordPress template):




The link means the thing still makes sense when JavaScript is not available and the script does the rest. One thing you need to do to show your useful tweets instead of mine is to change the class on the DIV! You get the number from your twitter page:

  • Go to your twitter page, f.e.: http://twitter.com/codepo8
  • Click the RSS link at the bottom
  • Check the URL of the feed, your ID is the number in between the slash and ‘.rss’, f.e.: http://twitter.com/statuses/user_timeline/13567.rss

The JavaScript for display of the badge is no rocket science whatsoever:


var tweets = function(){
var x = document.getElementById(‘mytweet’);
if(x){
var twitterUserId = x.className.replace(‘user-’,’‘);
var s = document.createElement(‘script’);
s.type = ‘text/javascript’;
s.src = ‘http://pipes.yahoo.com/pipes/pipe.run?’ +
‘_id=f7229d01b79e508d543fb84e8a0abb0d&_render=json’ +
‘&id=’ + twitterUserId + ‘&_callback=tweets.tweet’;
document.getElementsByTagName(‘head’)[0].appendChild(s);
};
function tweet(data){
if(data && data.value && data.value.items){
if(typeof data.value.items.length !== ‘undefined’){
var ul = document.createElement(‘ul’);
var all = data.value.items.length;
var end = all > 5 ? 5 : all;
for(var i=0;i < end;i++){
var now = data.value.items[i];
var li = document.createElement(‘li’);
var a = document.createElement(‘a’);
a.href = now.link;
a.appendChild(document.createTextNode(now.title));
li.appendChild(a);
ul.appendChild(li);
}

x.appendChild(ul);
}

}
};
return{
tweet:tweet
}

}();

  • We check if the element with the ID mytweet exists
  • We then extract the user ID from the class name and create a new JavaScript pointing to the JSON output of the pipe. This, once loaded, will call tweets.tweet() and send the data as JSON
  • The tweet() method checks if data was retrieved, creates a list of links and appends it to the DIV.

Hope this is useful to someone else, too.

Example of an unobtrusive, lazy-loading badge using the Twitter API

Friday, April 11th, 2008

Following questions I had about my talk at Highland Fling about badges for distribution and a twitter nagging by Tantek about the official twitter badge I thought I’d have a go at creating a twitter badge following some of the best practices I mentioned in my talk. Here’s the result.

The markup

Instead of HTML containers that will be seeded with real data when JavaScript is available and pointless when it isn’t, I wanted to build on top of HTML that makes sense without scripting and get all the info my script needs from there.





Example of a unobtrusive, lazy loading twitterbadge





In order to customise the badge, I allow for CSS classes with information to be added to the main container:





Example of a unobtrusive, lazy loading twitterbadge





They mean the following:

  • amount-n defines the amount of tweets to be displayed with n being an integer
  • skin-name defines the skin you want to use (for now this is grey and blue)
  • userinfo defines if the user’s avatar, name and location should be displayed.

The script

Here’s the full script and we’ll go through the bits one by one.

twitterbadge = function(){
var config = {
countDefault:5,
badgeID:’twitterbadge’,
userID:’twitterbadgeuser’,
tweetsID:’twitterbadgetweets’,
userinfo:’userinfo’,
stylesmatch:/skin-(w+)/,
amountmatch:/amount-(d+)/,
styles:{
‘grey’:’twitterbadge.css’,
‘blue’:’twitterbadgeblue.css’
}

};
var badge = document.getElementById(config.badgeID);
if(badge){
var link = badge.getElementsByTagName(‘a’)[0];
if(link){
var classdata = badge.className;
var head = document.getElementsByTagName(‘head’)[0];
var amount = config.amountmatch.exec(classdata);
var amount = amount ? amount[1] : config.countDefault;
var skin = config.stylesmatch.exec(classdata);
if(skin && skin[1]){
var style = document.createElement(‘link’);
style.setAttribute(‘rel’,’stylesheet’);
style.setAttribute(‘type’,’text/css’);
style.setAttribute(‘href’,config.styles[skin[1]]);
head.insertBefore(style,head.firstChild);
}

var name = link.href.split(‘/’);
var resturl = ‘http://twitter.com/statuses/user_timeline/’ +
name[name.length-1] + ‘.json?callback=’ +
‘twitterbadge.show&count=’ + amount;
var script = document.createElement(‘script’);
script.src = resturl;
script.type = ‘text/javascript’;
function show(result){
if(classdata.indexOf(config.userinfo) != -1){
var user = document.createElement(‘p’);
user.id = config.userID;
var img = document.createElement(‘img’);
img.src = result[0].user.profile_image_url;
img.alt = result[0].user.name;
user.appendChild(img);
var ul = document.createElement(‘ul’);
var data = [‘screen_name’,’name’,’location’];
for(var i=0;data[i];i++){
if(result[0].user[data[i]]){
var li = document.createElement(‘li’);
li.appendChild(document.createTextNode(result[0].user[data[i]]));
ul.appendChild(li);
}

}
user.appendChild(ul);
badge.appendChild(user);
}

var tweets = document.createElement(‘ul’);
tweets.id = config.tweetsID;
for(var i=0,j=result.length;i var username = result[i].user.screen_name;
var li = document.createElement(‘li’);
var span = document.createElement(‘span’);
span.innerHTML = result[i].text+’ ‘;
li.appendChild(span);
var link = document.createElement(‘a’);
link.setAttribute(‘href’,’http://twitter.com/’ + username +
‘/statuses/’+result[i].id);
link.appendChild(document.createTextNode(relative_time(result[i].created_at)));
li.appendChild(link);
tweets.appendChild(li);
}

badge.appendChild(tweets);
}

function relative_time(time_value) {
var values = time_value.split(” “);
time_value = values[1] + ” ” + values[2] + “, ” + values[5] + ” ” + values[3];
var parsed_date = Date.parse(time_value);
var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
var delta = parseInt((relative_to.getTime() – parsed_date) / 1000);
delta = delta + (relative_to.getTimezoneOffset() * 60);
if (delta < 60) {
return ‘less than a minute ago’;
} else if(delta < 120) {
return ‘about a minute ago’;
} else if(delta < (60*60)) {
return (parseInt(delta / 60)).toString() + ’ minutes ago’;
} else if(delta < (120*60)) {
return ‘about an hour ago’;
} else if(delta < (24*60*60)) {
return ‘about ’ + (parseInt(delta / 3600)).toString() + ’ hours ago’;
} else if(delta < (48*60*60)) {
return ‘1 day ago’;
} else {
return (parseInt(delta / 86400)).toString() + ’ days ago’;
}

}
}

}
return {
show:show,
init:function(){
head.appendChild(script);
}

};
}();
twitterbadge.init();

I am using the revealing module pattern to keep code short and avoid global callback methods. However, there is a slight Opera oddity with generated script nodes in module patterns so we have to deviate from the norm there with an extra init() method call after the main module.

The first thing I thought of providing is a configuration object for the script. This makes it easy to change settings of it without having to hunt through the whole script and is just a nice service for the implementer:


twitterbadge = function(){
var config = {
countDefault:5,
badgeID:’twitterbadge’,
userID:’twitterbadgeuser’,
tweetsID:’twitterbadgetweets’,
stylesmatch:/skin-(w+)/,
amountmatch:/amount-(d+)/,
styles:{
‘grey’:’twitterbadge.css’,
‘blue’:’twitterbadgeblue.css’
}

};

Here we have all the IDs in use, the style names and the corresponding file names and the regular expressions to get the data from the CSS class name. All of the IDs and classes are hooks to define your own skins. There is also a countDefault variable to define how many items should be shown when the amount class is not set.


var badge = document.getElementById(config.badgeID);
if(badge){
var link = badge.getElementsByTagName(‘a’)[0];
if(link){

I test for the badge and that it contains a link as this is where we will get all our configuration data from.


var classdata = badge.className;
var head = document.getElementsByTagName(‘head’)[0];
var amount = config.amountmatch.exec(classdata);
var amount = amount ? amount[1] : config.countDefault;
var skin = config.stylesmatch.exec(classdata);
if(skin && skin[1]){
var style = document.createElement(‘link’);
style.setAttribute(‘rel’,’stylesheet’);
style.setAttribute(‘type’,’text/css’);
style.setAttribute(‘href’,config.styles[skin[1]]);
head.insertBefore(style,head.firstChild);
}

Then I am ready to read the information from the class. I set a shortcut to the document head and read the amount of tweets to be displayed. If there is no amount-n class set I fall back to the default.

Next is the skin, I check if the class was set and if that is the case I create a new link element pointing to the right skin. I get the href from the configuration styles object.

Notice that I use insertBefore() to add the style to the head of the document and not appendChild(). This ensures to a degree that the skin css file will not override settings that might be in other stylesheets. The last included style sheet rules them all.


var name = link.href.split(‘/’);
var resturl = ‘http://twitter.com/statuses/user_timeline/’ +
name[name.length-1] + ‘.json?callback=’ +
‘twitterbadge.show&count=’ + amount;
var script = document.createElement(‘script’);
script.src = resturl;
script.type = ‘text/javascript’;

Now it is time to find the user name (by splitting the href attribute of the link) and assemble the REST url to get the twitter data. Normally I would have added the new script node to the head directly aftwerwards, but Opera doesn’t like this.


function show(result){
if(classdata.indexOf(config.userinfo) != -1){
var user = document.createElement(‘p’);
user.id = config.userID;
var img = document.createElement(‘img’);
img.src = result[0].user.profile_image_url;
img.alt = result[0].user.name;
user.appendChild(img);
var ul = document.createElement(‘ul’);
var data = [‘screen_name’,’name’,’location’];
for(var i=0;data[i];i++){
if(result[0].user[data[i]]){
var li = document.createElement(‘li’);
li.appendChild(document.createTextNode(result[0].user[data[i]]));
ul.appendChild(li);
}

}
user.appendChild(ul);
badge.appendChild(user);
}

Now it is time to start the core functionality: the show method that will be invoked by the twitter REST API callback. I check if the userinfo has been set and create the markup accordingly. Nothing amazing here.


var tweets = document.createElement(‘ul’);
tweets.id = config.tweetsID;
for(var i=0,j=result.length;i var username = result[i].user.screen_name;
var li = document.createElement(‘li’);
var span = document.createElement(‘span’);
span.innerHTML = result[i].text+’ ‘;
li.appendChild(span);
var link = document.createElement(‘a’);
link.setAttribute(‘href’,’http://twitter.com/’ + username +
‘/statuses/’+result[i].id);
link.appendChild(document.createTextNode(relative_time(result[i].created_at)));
li.appendChild(link);
tweets.appendChild(li);
}

badge.appendChild(tweets);
}

Next I get the tweets information, assemble a list and add it to the badge.


function relative_time(time_value) {
[...]
}

}
}

return {
show:show,
init:function(){
head.appendChild(script);
}

};
}();
twitterbadge.init();

The relative_time method is actually taken from the original twitter badge and calculates how old the tweets are. I end the module with a return statement that defines the public methods (in this case only show) and add the script node to call the REST API in an init method. This is only necessary to fix the Opera issue.

Download and Example

You can download the twitter badge and see it in action.

Making twitter multilingual with a hack of the Google Translation API

Monday, March 31st, 2008

After helping to fix the Yahoo search result pages with the correct language attributes to make them accessible for screen reader users I was wondering how this could be done with user generated content. The easiest option of course would be to ask the user to provide the right language in the profile, but if you are bilingual like me you actually write in different languages. The other option would be to offer me as the user to pick the language when I type it, which is annoying.

I then stumbled across Google’s Ajax Translation API and thought it should be very easy to marry it with for example the JSON output of the twitter API to add the correct lang attributes on the fly.

Alas, this was not as easy as I thought. On the surface it is very easy to use Google’s API to tell me what language a certain text is likely to be:


var text = “¿Dónde está el baño?”;
google.language.detect(text, function(result) {
if (!result.error) {
var language = ‘unknown’;
for (l in google.language.Languages) {
if (google.language.Languages[l] result.language) {
language = l;
break;
}

}
var container = document.getElementById("detection");
container.innerHTML = text + " is: " + language + "";
}

});

However, if you want to use this in a loop you are out of luck. The google.language.detect method fires off an internal XHR call and the result set only gives you an error code, the confidence level, a isReliable boolean and the language code. This is a lot but there is no way to tell the function that gets the results which text was analyzed. It would be great if the API repeated the text or at least allowed you to set a unique ID for the current XHR request.

As Ajax requests return in random order, there is no way of telling which result works for which text, so I was stuck.

Enter Firebug. Analyzing the requests going through I realized there is a REST URL being called by the internal methods of google.language. In the case of translation this is:


http://www.google.com/uds/GlangDetect?callback={CALLBACK_METHOD}&context={NUMBER}&q={URL_ENCODED_TEXT}&key=notsupplied&v=1.0

You can use the number and an own callback method to create SCRIPT nodes in the document getting these results back. The return call is:


CALLBACK_METHOD(‘NUMBER’,{“language” : “es”,”isReliable” : true,”confidence” : 0.24716422},200,null,200)

However, as I am already using PHP to pull information from another service, I ended up using curl for the whole proof of concept to make twitter speak in natural language:


    // curl the twitter feed
    $url = ‘http://twitter.com/statuses/public_timeline.rss’;
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $twitterdata = curl_exec($ch);
    curl_close($ch);
    // get all the descriptions
    preg_match_all(“/([^<]+)/msi”, $twitterdata,$descs);
    // skip the main feed description
    foreach($descs[1] as $key=>$d){
    if($key=0){
    continue;
    }

    // assemble REST call and curl the result
    $url = ‘http://www.google.com/uds/GlangDetect?callback=’ .
    ‘feedresult&context=’ . $key . ‘&q=’ . urlencode($d) .
    ‘&key=notsupplied&v=1.0’;
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $langcode = curl_exec($ch);
    curl_close($ch);
    // get the language
    preg_match(“/”language”:”([^”]+)”/”,$langcode,$res);
    // write out the list item
    echo ‘

  • ‘.$d.’
  • ‘;
    }

    ?>

Check out the result: Public twitter feed with natural language support

I will do some pure JavaScript solutions soon, too. This could be a great chance to make UGC a lot more accessible.

Thanks to Mark Thomas and Tim Huegdon for bouncing off ideas about how to work around the XHR issue.