Twitter and Laconica combined

I’ve written my first bit of javascript code. It’s designed to combine multiple twitter and laconica feeds into a single stream.

It uses jquery, so you’ll need to include that as well as this code. Then add a div with an id of “microblog”, which will get filled up with the tweets. The tweets are set to use the css class “tweet” so you can style them as you wish very easily.

It is currently set to read from twitter and identica, but any number of sites can be used. Just replace the urls near the bottom of the file with the ones you want to use.

Please do note this is the first javascript I’ve written so it is very likely to have stupid errors and really bad style in it. I welcome constructive criticism.

/*
 * twitter_identica – A program to combine twitter and laconica feeds
 *
 * @fileOverview A script to combine multiple twitter and laconica feeds.
 * @author Ghworg
 * @version 1.1
 *
 * Copyright (c) 2009 Ghworg
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 *
 */

/// @description Array to store the tweets in
var tweets = new Array();

/// @description Track which feed to pull from
var callCount = 0;

/**
 * @description Convert a time string to the age of the tweet
 * @param time The time string
 * @returns The age
 */

function dateToAge(time)  {
   var month = 1000 * 60 * 60 * 24 * 30;
   var week = 1000 * 60 * 60 * 24 * 7;
   var day = 1000 * 60 * 60 * 24;
   var hour = 1000 * 60 * 60;
   var minute = 1000 * 60;
   var second = 1000;
   
   var now = new Date();
   var then = new Date();
   then.setTime(Date.parse(time));

   var diff = now – then;
   var text = ;
   
   if (diff > month)  {
      text = Math.floor(diff / month) + ‘ months’;
   }
   else if (diff > week)  {
      text = Math.floor(diff / week) + ‘ weeks’;
   }
   else if (diff > day)  {
      text = Math.floor(diff / day) + ‘ days’;
   }
   else if (diff > hour)  {
      text = Math.floor(diff / hour) + ‘ hours’;
   }
   else if (diff > minute)  {
      text = Math.floor(diff / minute) + ‘ minutes’;
   }
   else  {
      text = Math.floor(diff / second) + ‘ seconds’;
   }
   text += ‘ ago’;
   
   return text;
}

/**
 * @description Make a JSON call
 * @param Url of handler to call
 * @param Event to call when complete
 * @param Event to call when an error is encountered
 */

function reqJSON(url, success, error)
{
   var CallParams = { };
   CallParams.type = "GET";
   CallParams.url = url;
   //CallParams.username = username;
   //CallParams.password = password;
   CallParams.processData = true;
   //CallParams.cache = false;
   CallParams.dataType = "json";
   CallParams.success = success;
   if (error)  {
      CallParams.error = error;
   }
   $.ajax(CallParams);
}

/**
 * @description Compare two tweets based on their timestamps
 * @param a First tweet
 * @param b Second tweet
 * @returns -1 if a is newer than b else 1
 */

function sortTweets(a, b)
{
   var timea = new Date();
   var timeb = new Date();
   timea.setTime(Date.parse(a.created_at));
   timeb.setTime(Date.parse(b.created_at));
   
   if (timea > timeb)  {
      return -1;
   }
   else  {
      return 1;
   }
}

/**
 * @description Strip off the end of a URL to get the site address
 * @param fullurl The URL http://www.example.com/some/other/text
 * @returns http://www.example.com/
 */

function getBaseUrl(fullurl)
{
   var stripped = ;
   if (fullurl.substr(0, 8) == ‘https://’)  {
      stripped = fullurl.substring(8);
   }
   else  {
      stripped = fullurl.substring(7);
   }
   var endIndex = stripped.search(‘/’);
   stripped = stripped.substr(0, endIndex);
   
   return stripped;
}

/**
 * @description Go through all the existing tweets and check if the new one
 *              is there already
 * @param oldTweets An array of all the existing tweets
 * @param newTweet The new tweet
 * @returns true or false
 */

function checkForDups(oldTweets, newTweet)
{
   var dupe = false;
   $.each(oldTweets, function(i, tweet)  {
      if (tweet.text == newTweet.text)  {
         dupe = true;
      }
   });
   return dupe;
}
   

/**
 * @description Search the text for links and make them clickable
 * @param startMarker Text to look for that indicates a link
 * @param text The original text
 * @param prefix Text to put in front of the URL
 * @returns Text with links
 */

function addLink(startMarker, text, prefix)
{
   var endindex = 0;
   while (endindex < text.length)  {
      startindex = text.indexOf(startMarker, endindex);
      if (startindex >= 0)  {
         endindex = text.indexOf(‘ ‘, startindex);
         if (endindex < 0)  {
            endindex = text.length;
         }
         before = text.substring(0, startindex);
         url = text.substring(startindex, endindex);
         after = text.substring(endindex);
         href = url.link(prefix + url.replace(‘@’, ));
         text = before + href + after;
         endindex = before.length + href.length
      }
      else  {    
         break;
      }
   }
   return text;
}

/**
 * @description Convert http https and @ replies to links
 * @param text The original text
 * @param baseUrl Site the tweet came from (to use for @s)
 * @returns Text with links
 */

function addLinks(text, baseUrl)
{
   text = addLink(‘https://’, text, );
   text = addLink(‘http://’, text, );
   text = addLink(‘@’, text, baseUrl);
   return text
}

/**
 * @description JSON request succeeded,
 *              process the data and insert it into the web page
 * @param json The json data received
 * @param textStatus Result code
 */

function readJson(json, textStatus)
{
   //Successful call
   //Do stuff with the JSON
   
   var requestUrl = this.url;
   var baseUrl = getBaseUrl(requestUrl);
   
   // Clear out any old tweets from the html
   $("p.tweet").remove();
   
   // Delete old tweets from this url from the list
   var oldtweets = tweets;
   tweets = new Array();
   $.each(oldtweets, function(i, item)  {
      if (item.baseUrl != baseUrl)  {
         tweets[tweets.length] = item;
      }
   });
   
   // Add newly retrieved tweets to the list
   $.each(json, function(i, item)  {
      item.baseUrl = baseUrl;
      if (checkForDups(tweets, item) == false)  {
         tweets[tweets.length] = item;
      }
   });
   
   // Sort the tweet list by age
   tweets.sort(sortTweets);
   
   // Print the tweets to the html
   var pretext = ‘<p class="tweet">’;
   var postext = ‘</p>’;
   $.each(tweets, function(i, item)  {
      //pretext = ‘<p class="tweet ‘;
      //pretext = pretext + item.baseUrl.replace(‘.’, ”) + ‘">’;
      var ago = dateToAge(item.created_at);
      var base = ‘http://’ + item.baseUrl + ‘/’;
      var text = addLinks(item.text, base);
      var userlink = item.user.screen_name.link(base + item.user.screen_name);
      mb.append(pretext + ago + ‘: ‘ + userlink + ‘: ‘ + text + postext);
   });
}

/**
 * @description Called if JSON request failed
 * @param XMLHttpRequest
 * @param textStatus
 * @param errorThrown
 */

function failJson(XMLHttpRequest, textStatus, errorThrown)
{
   // typically only one of textStatus or errorThrown
   // will have info
   //
   mb.append("Error getting json data
"
);
}

/**
 * @description Main function
 */

function display() {
   mb = $("#microblog");
   
   var urls = [ "https://identi.ca/api/statuses/friends_timeline.json?callback=?",
                "https://twitter.com/statuses/friends_timeline.json?callback=?"
               ];
   
   //if ( 0 == $("p.tweet").length )  {
   //   $.each(urls, function(i, url)  {
   //      reqJSON(url, readJson, failJson);
   //   });
   //}
   //else  {
   if (callCount < urls.length)  {
      reqJSON(urls[callCount], readJson, failJson);
      callCount = callCount + 1;
   }
   else  {
      callCount = 0;
   }
   //}
}

 

Update (2009-02-22): I forgot to mention, you need t call the display function to get it to actually do something. It is currently set to read one feed every time display is called as it is designed to be called on a timer and I didn’t want two password requests to appear at once. If you want to read all feeds simultaneously then uncomment the commented code in the display function.

Update (2009-02-28): Fixed a small bug where html tags in tweets were interpreted, they are now escaped. New version is available twitter_identica.