Friend Feed Who Are You

By engtech Last update May 2, 2008 — Installed 1,347 times. Daily Installs: 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0
/*
  --------------------------------------------------------------------
  How to Install a Greasemonkey Script
  --------------------------------------------------------------------

  - You need the Firefox web browser - Download and install
  http://www.mozilla.com/en-US/firefox/

  - You need to install Greasemonkey - How-To
  http://internetducttape.com/2007/08/23/howto-install-firefox-extensions-screenshots/

  - Install this script - How-To
  http://internetducttape.com/2007/08/24/howto-use-firefox-greasemonkey-userscripts-screenshots/

*/


// ==UserScript==
// @name           Friend Feed Who Are You
// @namespace      http://internetducttape.com
// @description    Adds a 'who are you' link at the top of every users' profile page.
// @include        http://friendfeed.com*
// @version        0.4
// ==/UserScript==

    // Some of this code taken from Aviv Shaham's Show Likes script:
    //    http://ffapps.com/showlikes/
    // and from Aviv Shaham's Filter and Groups script
    //    http://ffapps.com/filters/
    //
    // Namely: fetching username, remotekey, and subscriber info.

var friendfeedwhoareyou_version = "0.4";

(function() {
  // Handy dandy auto-updater I use for all my scripts.
  autoUpdateFromUserscriptsDotOrg({
    name: 'Friend Feed Who Are You',
	url: 'http://userscripts.org/scripts/source/24768.user.js',
	version: friendfeedwhoareyou_version,
	});

  var usage_count = GM_getValue('IDT_WHOAREYOU_USAGE', 0);

  var re = {
    'remoteKey': 	/remote key:.*[\s]?.*>([\w]+)</,
    'username':		/\/([\w]+)\"/,
    'uuid':				/([\w]{8}(?:-[\w]{4}){3}-[\w]{12})/
  };

  var user = {
    'uuid':null,
    'username':null,
    'AT':null,
    'remoteKey':null,
    'subscriptions':null
  };

  var time = new Date().getTime().toString();

  function jquery_wait() {
    if(typeof unsafeWindow.jQuery == 'undefined') { window.setTimeout(jquery_wait,100); }
    else { $ = unsafeWindow.jQuery; friendFeedWhoAreYou(); }
  }
  jquery_wait();

  function friendFeedWhoAreYou() {   
    // Utility functions for setting GM variables even from unsafeWindow
    function getGMValue(k,d) { if(typeof d=='undefined'){d=null;} return GM_getValue(k,d); }
    function setGMValue(k,v) { window.setTimeout(function() { GM_setValue(k,v);}, 0); return }

    // fetch the remote key with caching
    function cacheRemoteKey() {
      function fetchRemoteKey() {
	$.ajax({
	  url:"http://friendfeed.com/account/api",
	      cache:false,
	      success:function(html){
	      M = html.match(re['remoteKey']);
	      user['remoteKey'] = M ? M[1] : '';
	      setGMValue('IDT_WHOAREYOU_REMOTEKEY', user['remoteKey'].toSource());
	      setGMValue('IDT_WHOAREYOU_REMOTEKEY_TIMER', time);
	    }});
      }

      if (isValueExpired('IDT_WHOAREYOU_REMOTEKEY_TIMER', 30*60*1000)) {
	fetchRemoteKey();
      }
      else {
	user['remoteKey'] = eval(GM_getValue('IDT_WHOAREYOU_REMOTEKEY'));
      }
    }

    // Set up HTTP basic authorization
    function friendFeedAuth() {
      u = user['username'];
      p = user['remoteKey'];
      //    alert(u+' '+p+' '+Base64.encode(u+':'+p));
      return "Basic " + Base64.encode(u+':'+p);
    }

    // Call this function every time someone interacts with the script.
    // It'll automatically share itself every X usages. People who use
    // the script a lot will promote it, people who don't use it that
    // often won't promote it. It's fair.
    function selfPromote() {
      usage_count += 1;
      setGMValue('IDT_WHOAREYOU_USAGE', usage_count);
      // Play around with these numbers to control how often the script
      // promotes itself.
      if (25 == (usage_count % 100)) {
	$.ajax({
	  url:"/api/share",
	      type:'POST',
	      beforeSend: function(req) {
	      req.setRequestHeader('Authorization', friendFeedAuth());
	    },
	      data: {
	    title:"Friend Feed - Who Are You? (version "+friendfeedwhoareyou_version+")",
		link:"http://internetducttape.com/2008/04/10/friendfeed-scripts-unsubscribe-who-service/#whoareyou",
		comment:"See more information about who people are on Friend Feed with this handy script for Firefox. I've used it "+usage_count+" times. This is an automated message.",
		via:"internetducttape",
		}
	    //	  , success: function() { alert("Thank you for sharing!"); }
	  });
      }
    }

    // Utility function to check if a value is expired
    function isValueExpired(GMVal, maxDuration) {
      var lastChecked = GM_getValue(GMVal, null);
      return (!lastChecked || (time - lastChecked) > maxDuration);
    }

    // Get the logged in username and user subscriptions.
    function fetchUserInfo() {
      user['username'] = unsafeWindow.gUserLink ? unsafeWindow.gUserLink.match(re['username'])[1] : null;

      if (isValueExpired('IDT_WHOAREYOU_SUBSCRIPTIONS_TIMER', 30*60*1000)) {
	fetchSubscriptions(user['username'], 0, 0);
      }
      else {
	user['subscriptions'] = eval(GM_getValue('IDT_WHOAREYOU_SUBSCRIPTIONS'));
      }
    }

    // Fetch the subscriptions and update the display.
    // user_mode = 0 initialize the logged in user
    // user_mode = 1 get someone else
    // display_mode = 1 for on the l_popup window
    // display_mode = 0 for on the profile page
    function fetchSubscriptions(username, user_mode, display_mode) {
      function callbackSortNames(a,b) {
	if (a.name.toLowerCase() == b.name.toLowerCase()) {
	  if (a.nickname == b.nickname)
	    return 0;
	  return (a.nickname < b.nickname) ? -1 : 1;
	}
	return (a.name.toLowerCase() < b.name.toLowerCase()) ? -1 : 1;
      }

      function linkSubNames(arr, display_mode) {
	if (arr.length == 0) {
	  return '';
	}
	// performance hit, don't create the links if the array is too big
	if ((display_mode == 0) && (arr.length < 500)) {
	  return '<p>'+arr.map(function(x) {
					   return '<a class="friend l_popup" uid="'+x['id']+'" href="'+x['profile_url']+'">'+x['name']+'</a>';
					 }).join(', ')+'</p>';
	}
	else {
	  return '<p>'+arr.map(function(x) {
					   return x['name'];
					 }).join(', ')+'</p>';
	}
      }

      function linkSubImgs(arr) {
	if (arr.length == 0) {
	  return '';
	}
	if (arr.length > 500) {
	  return '<p>Too many friends to show pictures of.</p>';
	}
	// /mathewingram/picture?size=medium&v=1
	return '<p>'+arr.map(function(x) {
				    return '<img height="25" width="25" src="/'+x['nickname']+'/picture?size=small&v=1">';
				  }).join(' ')+'</p>';	
      }

      function displaySub(arr, display_mode) {
	return linkSubNames(arr, display_mode) + linkSubImgs(arr);
      }

      var subscriptions = [];
      $.ajax({
	url:"http://friendfeed.com/api/user/"+username+"/profile",
	    cache:false,
	    dataType:'json',
	    complete:function(xhr) {
	    if (xhr.status == 200) {
	      var json = eval("("+xhr.responseText+")");
	      $.each(json['subscriptions'], function(){
		       subscriptions.push({
			 id:this['id'],
			     name:this['name'],
			     nickname:this['nickname'],
			     profile_url:this['profileUrl']
			     });
		     });
	      subscriptions.sort(callbackSortNames);
	      // Cache the user's subscriptions
	      if (user_mode == 0) {
		//	      setGMValue('subscriptions', subscriptions.toSource());
		//	      setGMValue('lastSubscriptionsFetch', time);
		user['subscriptions'] = subscriptions;
		setGMValue('IDT_WHOAREYOU_SUBSCRIPTIONS', user['subscriptions'].toSource());
		setGMValue('IDT_WHOAREYOU_SUBSCRIPTIONS_TIMER', time);
	      }
	      // Compare against this guy
	      else if (user_mode == 1) {
		var my_subs = user['subscriptions'].map(function(x) {return x['nickname']});
		var our_subs = [];
		var their_subs = [];
		$.each(subscriptions, function(i, val) {
			 if(-1 != $.inArray(val['nickname'], my_subs)) {
			   our_subs.push(val);
			 }
			 else {
			   their_subs.push(val);
			 }
		       });
		var our_display = '';
		var their_display = '';
		if (our_subs.length > 0) {
		  our_display = '<h4>Common Friends</h4>' + displaySub(our_subs, display_mode);
		}
		// Don't display their friends in the mini-popup
		if ((their_subs.length > 0) && (display_mode == 0)) {
		  their_display = '<h4>Uncommon Friends</h4>' + displaySub(their_subs, display_mode);
		}
		my_subs = null;
		their_subs = null;
		$('.idt-whoareyou-subscriptions-'+username).html('<p>They have <b>'+subscriptions.length+'</b> friends.</p><p>You have <b>'+our_subs.length+'</b> friends in common.</p>'+our_display+their_display);
		our_subs = null;
		subscriptions = null;
		our_display = null;
		their_display = null;
	      }
	    }
	  }
	});
    }


    // Load the username/remotekey so we can use the API.
    if (null == user['remoteKey']) { cacheRemoteKey(); }
    if (null == user['username']) { fetchUserInfo(); }

    // Describe who a user is based on their services and their friends.
    // display_mode = 1 for on the l_popup window
    // display_mode = 0 for on the profile page
    function friendFeedDescribe(display_mode, username) {
      // Depending on the display_mode, we will use different class names for inserting things.
      var info_class = display_mode ? '.userpopup .services' : '.section:first';
      var insert_class = display_mode ? '.userpopup .subscribe' : '.userlisttop .username';
      var sub_style = display_mode ? 'margin-right: 50px;' : 'padding-right: 250px';

      // Build up the code to be inserted.
      // Have to set the href to javascript:; instead of # because I do not want to go to the top of the page.
      var insert = '<div class="idt-whoareyou"><div class="idt-whoareyou-toggle-'+username+'"><a class="idt-whoareyou-show idt-whoareyou-toggle-'+username+'" href="javascript:;">Who is this person?</a></div><div class="idt-whoareyou-toggle-'+username+'" style="display:none;"><ul style="list-style:none;">';
      $(info_class+' img.icon[alt]').each( function(i) {
					     var alt = $(this).attr('alt');
					     var href = $(this).parents('a').attr('href');
					     var s = alt.replace(/\./g,'').toLowerCase();
					     // For some of the services there is no reason to display
					     // their information because the URL doesn't link to a profile.
					     if ((alt != 'Amazon.com') &&
						 (alt != 'Gmail/Google Talk') &&
						 (alt != 'Netflix')) {
					       insert += '<li><a service="'+s+'" class="l_idt_byservice" href="#">'+$(this).parent().html()+'</a> <b>'+alt+':</b> <a title="'+alt+'" href="'+href+'">'+href+'</a></li>';
					     }
					   });
      insert += '<div class="idt-whoareyou-subscriptions-'+username+'" style="'+sub_style+'"></div></ul><p><a class="idt-whoareyou-hide idt-whoareyou-toggle-'+username+'" href="javascript:;">Hide this information</a></p></div></div>';
      $(insert_class).after(insert);
      insert = null;
      // Click handler for the toggle
      //       $('a.idt-whoareyou-toggle').click(function(){
      // Use the FriendFeed click handler

      $('a.idt-whoareyou-toggle-'+username).click(function(){
				    if($('.idt-whoareyou-subscriptions-'+username).html() == "") {
				      fetchSubscriptions(username, 1, display_mode);
				    }
				    selfPromote();
				    $('div.idt-whoareyou-toggle-'+username).toggle();
				  });
    }

    if ((document.location.pathname != '/') &&
	(document.location.pathname != '/public')
	) {
      if ($('.userlisttop')) {
	var username = unsafeWindow.gPath.slice(1);
	// Set vertical-align top to fix the profile image
	$('.userlisttop td').css('vertical-align','top')
	friendFeedDescribe(0, username);
      }
    }
    // Overwrite the userpopup.makeShadow method to add whoareyou description.
    if (typeof unsafeWindow.userpopup != 'undefined') {
      if (typeof unsafeWindow.userpopup.makeShadowWhoAreYou == 'undefined') {
	unsafeWindow.userpopup.makeShadowWhoAreYou = unsafeWindow.userpopup.makeShadow;
	unsafeWindow.userpopup.makeShadow = function() {
	  var B = $("#userpopup");
	  if (B.length == 0) {
	    return;
	  }
	  if (B.find('a:first')) {
	    var username = B.find('a:first').attr('href').slice(1);
	    // Stick in my "Who Are You" link?
	    friendFeedDescribe(1, username);
	  }
	  // Call the original.
	  unsafeWindow.userpopup.makeShadowWhoAreYou();
	}
      }
    }
  };

  /**
   *
   *  Base64 encode / decode
   *  http://www.webtoolkit.info/
   *
   **/

  var Base64 = {

    // private property
    _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    // public method for encoding
    encode : function (input) {
      var output = "";
      var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
      var i = 0;

      input = Base64._utf8_encode(input);

      while (i < input.length) {

	chr1 = input.charCodeAt(i++);
	chr2 = input.charCodeAt(i++);
	chr3 = input.charCodeAt(i++);

	enc1 = chr1 >> 2;
	enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
	enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
	enc4 = chr3 & 63;

	if (isNaN(chr2)) {
	  enc3 = enc4 = 64;
	} else if (isNaN(chr3)) {
	  enc4 = 64;
	}

	output = output +
	  this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
	  this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

      }

      return output;
    },

    // public method for decoding
    decode : function (input) {
      var output = "";
      var chr1, chr2, chr3;
      var enc1, enc2, enc3, enc4;
      var i = 0;

      input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

      while (i < input.length) {

	enc1 = this._keyStr.indexOf(input.charAt(i++));
	enc2 = this._keyStr.indexOf(input.charAt(i++));
	enc3 = this._keyStr.indexOf(input.charAt(i++));
	enc4 = this._keyStr.indexOf(input.charAt(i++));

	chr1 = (enc1 << 2) | (enc2 >> 4);
	chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
	chr3 = ((enc3 & 3) << 6) | enc4;

	output = output + String.fromCharCode(chr1);

	if (enc3 != 64) {
	  output = output + String.fromCharCode(chr2);
	}
	if (enc4 != 64) {
	  output = output + String.fromCharCode(chr3);
	}

      }

      output = Base64._utf8_decode(output);

      return output;

    },

    // private method for UTF-8 encoding
    _utf8_encode : function (string) {
      string = string.replace(/\r\n/g,"\n");
      var utftext = "";

      for (var n = 0; n < string.length; n++) {

	var c = string.charCodeAt(n);

	if (c < 128) {
	  utftext += String.fromCharCode(c);
	}
	else if((c > 127) && (c < 2048)) {
	  utftext += String.fromCharCode((c >> 6) | 192);
	  utftext += String.fromCharCode((c & 63) | 128);
	}
	else {
	  utftext += String.fromCharCode((c >> 12) | 224);
	  utftext += String.fromCharCode(((c >> 6) & 63) | 128);
	  utftext += String.fromCharCode((c & 63) | 128);
	}

      }

      return utftext;
    },

    // private method for UTF-8 decoding
    _utf8_decode : function (utftext) {
      var string = "";
      var i = 0;
      var c = c1 = c2 = 0;

      while ( i < utftext.length ) {

	c = utftext.charCodeAt(i);

	if (c < 128) {
	  string += String.fromCharCode(c);
	  i++;
	}
	else if((c > 191) && (c < 224)) {
	  c2 = utftext.charCodeAt(i+1);
	  string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
	  i += 2;
	}
	else {
	  c2 = utftext.charCodeAt(i+1);
	  c3 = utftext.charCodeAt(i+2);
	  string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
	  i += 3;
	}

      }

      return string;
    }

  }

 })();

function autoUpdateFromUserscriptsDotOrg(SCRIPT) {
  // Update code from Junk Blocker: http://loonyone.livejournal.com/
  // usage example
  // autoUpdateFromUserscriptsDotOrg({
  //   name: 'RSS+Atom Feed Subscribe Button Generator',
  //   url: 'http://userscripts.org/scripts/source/688.user.js',
  //   version: "1.2",
  // });
  try {
    if (!GM_getValue) return; // Older version of Greasemonkey. Can't run.

    // avoid a flood of dialogs e.g. when opening a browser with multiple tabs set to homepage
    // and a script with * includes or opening a tabgrop
    var DoS_PREVENTION_TIME = 2 * 60 * 1000;
    var isSomeoneChecking = GM_getValue('CHECKING', null);
    var now = new Date().getTime();
    GM_setValue('CHECKING', now.toString());

    if (isSomeoneChecking && (now - isSomeoneChecking) < DoS_PREVENTION_TIME) return;

    // check daily
    var ONE_DAY = 24 * 60 * 60 * 1000;
    var ONE_WEEK = 7 * ONE_DAY;
    var TWO_WEEKS = 2 * ONE_WEEK;
    var lastChecked = GM_getValue('LAST_CHECKED', null);
    if (lastChecked && (now - lastChecked) < TWO_WEEKS) return;

    GM_xmlhttpRequest({
      method: 'GET',
	  url: SCRIPT.url + '?source', // don't increase the 'installed' count just for update checks
	  onload: function(result) {
	  if (!result.responseText.match(/@version\s+([\d.]+)/)) return;     // did not find a suitable version header

	  var theOtherVersion = parseFloat(RegExp.$1);
	  if (theOtherVersion <= parseFloat(SCRIPT.version)) return;      // no updates or older version on userscripts.orge site

	  if (window.confirm('A new version ' + theOtherVersion + ' of greasemonkey script "' + SCRIPT.name + '" is available.\nYour installed version is ' + SCRIPT.version + ' .\n\nUpdate now?\n')) {
	    GM_openInTab(SCRIPT.url);   // better than location.replace as doing so might lose unsaved data
	  }
	}
      });
    GM_setValue('LAST_CHECKED', now.toString());
  } catch (ex) {
  }
}