// ==UserScript==
// @name           Facebook Purity
// @namespace      http://steeev.freehostia.com
// @description    Removes application spam from your facebook homepage
// @include        http://www.facebook.com/*
// @include        http://www.new.facebook.com/*
// @exclude        http://www.facebook.com/ajax/*
// @exclude        http://www.new.facebook.com/ajax/*
// @version        2.2b - 20th Nov 2009
// ==/UserScript==

//
// (C) stephen fernandez 2009        http://steeev.freehostia.com/wp/
//

// If you like this script please donate, big or small donations, anything is welcome
// http://steeev.freehostia.com/donate/

// ------------------------------------------------------------
// Facebook Purity Home Page : http://bit.ly/fbpure
// Facebook Purity Fan Page  : http://www.facebook.com/fbpurity
// ------------------------------------------------------------

// ABOUT
// -----
// This greasemonkey script hides all third party facebook application messages from your fb homepage.
// To allow a specific application's messages to be seen, add its application id to the whitelist in the code below.
// Instructions on how to edit the whitelist are available here: http://bit.ly/fbpwhitelist

// INSTALLATION
// ------------
// This script is compatible with the following web browsers: Firefox, Google Chrome, Opera and Safari
//
// Firefox script installation instructions : First install the greasemonkey plugin from here: https://addons.mozilla.org/firefox/addon/748
//                                            Then restart the browser, then reload this script and you will be asked if you want to install the script, click ok + thats it.
//
// Google Chrome script installation instructions: http://bit.ly/chromegm
//        Safari script installation instructions: http://bit.ly/safarigm
//         Opera script installation instructions: http://bit.ly/operagm

// UPDATES
// -------
// 1.51  30th March 2009 Bug fixed: if there were no pending requests, the script didnt work
// 1.52   4th April 2009 removed GM_addStyle command, for better compatibility with other browsers (chrome + opera)
// 1.53  26th April 2009 changed insertpoint so its not dependent on suggestions box
// 1.54  27th April 2009 script is now compatible with Google Chrome + Opera (and possibly safari, not tested yet)
// 1.54a  5th May   2009 fixed a minor bug
// 1.54b 24th June  2009 fixed foracebook code change
// 1.54c 29th July  2009 fixed for facebook code change
// 1.54d  5th Aug   2009 fixed for facebook code change
// 1.54e 26th Aug   2009 fixed for facebook code change
// 1.60   4th Sep   2009 added application whitelisting capability, the default whitelisted apps are:
//                       FB Iphone, Selective Twitter and Living Social
// 1.61  14th Oct   2009 optimised script, it should run faster and more efficiently now
//                       added tumblr, digsby and tweetdeck to whitelist, removed livingsocial from whitelist
// 1.8   21st Oct   2009 added "block app" functionality and ability to show just the app messages on the page
//                       added blackberry app to default whitelist
// 1.8a  22nd Oct   2009 fixed bug with blocking apps if language was not set to english
// 1.8d  23rd Oct   2009 fixed for new facebook update ( "live feed" changes )
//                       fixed if you click "Show x similar posts" on an application message, when you have chosen to display the app messages, the app messages will no longer be automatically hidden
//                       if after you've blocked an app, and there are no more hidden app messages, it now returns to normal homepage view
// 1.8e  26th Oct   2009 script now hides more apps (ones that use widgets) and should also now be restricted to running on the homepage
// 1.9   28th Oct   2009 added filter for "extra" [joined group, became fan, attending event, became friend] messages
// 2.0   31st Oct   2009 fixed Show/Hide Logic, made app+extra filters mutually exclusive, optimised the script a lot
//                       moved fbpurity bar to the left to regain vertical space on the right hand column
//                       newly added elements now obey the current filter
// 2.1   2nd  Nov   2009 reset show/hide mode to default when user changes fb filter
//                       added workaround for fb still showing apps that you have "blocked", script now "hides" app as well as blocking it, when you click the "block app" link
//                       fixed bug with whitelisted apps being displayed via the "show hidden apps" filter
//                       fixed - block link wasnt showing up on dynamically inserted app messages
// 2.1a  4th  Nov   2009 fixed - now also hides apps that dont have a mini-icon
// 2.1b  7th  Nov   2009 added option to hide suggestions box
//                       added facebook for android to whitelist
// 2.2  12th  Nov   2009 rewrote filter system, should block absolutely all apps now (except whitelisted ones)
//                       added filter for photos posted by applications (eg farmville, top friends etc)
// 2.2a 12th  Nov   2009 fixed suggestion box filter
// 2.2b 20th  Nov   2009 added another "became friends" story id to extras list

//
// (C) stephen fernandez 2009      http://steeev.freehostia.com/wp/
//

// If you like this script please donate, big or small donations, anything is welcome
// http://steeev.freehostia.com/donate/

var fbpVersion = "v2.2b";

// APPLICATION WHITELIST
// ---------------------

// If you would like to edit the list of applications in the whitelist, 
// theres detailed instructions here: http://bit.ly/fbpwhitelist

// Add the application IDs of the facebook applications you dont want hidden by fb purity to the whitelist below,
// separated by commas. You can get an application's ID by going to the application page, and looking for 
// the number after the "application.php?id=" bit in the URL. You only want the number part, once you have it, paste
// it into the list, make sure each numeric ID is separated from the others by a comma (no spaces).

var whitelist="6628568379,115463795461,48119224995,5895217474,56212371378,146139331013,2254487659,2915120374,74769995908";

// the application ids in the default whitelist above belong to, in order:
// 6628568379   = facebook iphone application
// 115463795461 = selective twitter
// 48119224995  = tumblr
// 5895217474   = digsby
// 56212371378  = tweetdeck
// 146139331013 = tweetdeck for iphone
// 2254487659   = facebook for blackberry
// 2915120374   = facebook mobile widget
// 74769995908  = facebook for android

var suggestionsbox=1; // if you dont want to see the suggestions box, change the value of suggestionsbox to zero;

// STORIES
var APPLICATION_STORY = 237 ;

// EXTRA STORIES ( if you want to see the following "extra" type stories in your feed, change their values to zero - see http://bit.ly/fbpextras for more info )
var JOINED_GROUP_STORY    = 21 ;
var BECAME_FAN_STORY      = 161 ;
var BECAME_FRIENDS_STORY  = 8 ;
var BECAME_FRIENDS_STORY2 = 12 ;
var ATTEND_EVENT_STORY    = 38 ;
var ATTEND_EVENT_STORY2   = 178 ;
var CHANGED_RELATIONSHIP_STORY = 10 ;


// APPLICATION STORY SPECIAL CASES (some apps are posting photos to the feed)
var TAGGED_IN_ALBUM_STORY=65;
var PHOTO_STORY=247;
var PHOTO_COMMENT_STORY=7;

var extras_blacklist = JOINED_GROUP_STORY + ',' + BECAME_FAN_STORY + ',' + BECAME_FRIENDS_STORY + ',' + BECAME_FRIENDS_STORY2 + ',' + ATTEND_EVENT_STORY + ',' + ATTEND_EVENT_STORY2;
var arr_extras_blacklist=extras_blacklist.split(',');
var arr_extras_blacklist2={};
for (i in arr_extras_blacklist)
  arr_extras_blacklist2[arr_extras_blacklist[i]]=true; // create hashmap

var arrwhitelist=whitelist.split(',');
var arrwhitelist2={};
for (i in arrwhitelist)
  arrwhitelist2[arrwhitelist[i]]=true; // create hashmap

// sort out cross browser issue ( firefox, chrome, opera + safari?)
try {
  if (typeof contentWindow.Env != 'undefined')
    unsafeWindow=contentWindow; // google chrome
} 
catch (e) {
  try {
    if (typeof unsafeWindow.Env != 'undefined')
      ; // firefox
  }
  catch (e) {
    unsafeWindow=window; // opera + safari?
  }
}

window.addEventListener('load', 
 function () {
    
  var crappyappmsgcounter=0;
  var xtramsgcounter=0;
  var fbpstyle=document.createElement('style');
  
  fbpstyle.setAttribute('id','fbpstyler');
  fbpStyleApps= ".fbpblocked {border-style: dashed; border-width:1px; border-top: 0px !important; border-color: pink; display:block !important} .fbpblockedx {display:none !important} .fbpnormal {display:none !important}";
  fbpStyleExtras= ".fbpblocked {display:none !important} .fbpblockedx {border-style: dashed; border-width:1px; border-top: 0px !important; border-color: lightblue ; display:block !important} .fbpnormal {display:none !important}";
  fbpStyleNormal= ".fbpblocked {display:none !important} .fbpblockedx {display:none !important} .fbpnormal {display:block !important}";
  
  fbpstyle.textContent=fbpStyleNormal; //'.fbpblocked {border-style: dashed; border-width:1px; border-top: 0px !important; border-color: pink} .fbpblockedx {border-style: dashed; border-width:1px; border-top: 0px !important; border-color: lightblue} .fbpnormal {display:block !important}';
  if(document.getElementsByTagName('head'))
    document.getElementsByTagName('head')[0].appendChild(fbpstyle);
  
  document.addEventListener("DOMNodeInserted", fpInsertedNodeDomHandler, false);

  function fpInsertedNodeDomHandler(event) {
    if(document.getElementById('home_stream') && event.target.getElementsByClassName)  // && ((event.target.className.indexOf('GenericStory')!=-1) ||(event.target.getElementsByClassName('GenericStory')) 
      cleartheshizzle(event.target);
  }
  
  function updateblockedcount() {
    crappyappmsgcounter=document.getElementsByClassName('fbpblocked').length;
    var fbpblockcountspan=document.getElementById('fbpblockcount');
    if(fbpblockcountspan) 
      fbpblockcountspan.innerHTML=crappyappmsgcounter;
      
    xtramsgcounter=document.getElementsByClassName('fbpblockedx').length;
    var fbpblockxcountspan=document.getElementById('fbpblockxcount');
    if(fbpblockxcountspan) 
      fbpblockxcountspan.innerHTML=xtramsgcounter;
  }
  
  function destroyblockedappstories (appid) {

    var appnodes=document.getElementsByClassName('aid_' + appid);
    while(appnodes.length){
      appnodes=document.getElementsByClassName('aid_' + appid);
      appnodes[0].parentNode.removeChild(appnodes[0]);
      if(fpbblockcountspan)
          fpbblockcountspan.innerHTML=--crappyappmsgcounter;
    }
    //check if there are any blocked msgs left and if not, return to normal homepage view
    blockedmsgs=document.getElementsByClassName('fbpblocked');
    if(!blockedmsgs.length) {
      fbpshowblocked();
    }
    appnodes=null;
    
  }// END destroyblockedappstories function

  function hideapp (appid) {
    // we need to hide the app as well as block it, due to facebook still displaying the blocked apps!
    var http = new XMLHttpRequest();
    var url="http://" + location.hostname + "/ajax/feed/filter_action.php" ;
    var params = "filter_key=h&action=unfollow&value=" + appid + "&post_form_id=" + unsafeWindow.Env.post_form_id + "&fb_dtsg=" + unsafeWindow.Env.fb_dtsg  + "&post_form_id_source=AsyncRequest&__a=1";    
    http.open("POST", url, true);
    http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    http.setRequestHeader("Content-length", params.length);
    http.setRequestHeader("Connection", "close");

    http.onreadystatechange = function() {
      if(http.readyState == 4 && http.status == 200) {
        http=null;
      }
    }
    http.send(params);

  } // endhideapp function

  function blockapp(appid, appname) {
    //unsafeWindow.pages_show_block_app("49572793475", "block", "about");
     
    if (!confirm("Do you want to block the following application : '" + appname +"'"))
      return;
      
    var http = new XMLHttpRequest();
    //var url = "http://www.facebook.com/apps/block.php?id=" + appid + "&action=block&source=about";
    var url = "http://" + location.hostname + "/ajax/apps/block.php";
    var params = "action=block&app_id=" + appid + "&save=Block%20" + escape(appname) + "&post_form_id=" + unsafeWindow.Env.post_form_id + "&fb_dtsg=" + unsafeWindow.Env.fb_dtsg  + "&post_form_id_source=AsyncRequest&__a=1";
    http.open("POST", url, true);
    http.setRequestHeader("Referer", "http://" + location.hostname + "/apps/application.php?id=" + appid);
    http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    http.setRequestHeader("Content-length", params.length);
    http.setRequestHeader("Connection", "close");

    http.onreadystatechange = function() {
  	  if(http.readyState == 4 && http.status == 200) {
		    if(http.responseText.match('\"error\":0')){
		      //unsafeWindow.console.log("successfully blocked app : '" + appname +  "' with id=" +appid + "\n");
		      destroyblockedappstories (appid);
		      hideapp(appid);
		    }
		    else
		      alert('app block failed for some reason:\n\n' + http.responseText);
		    http=null;
	    }
    }
  http.send(params);
  
  } // END blockapp function

  function blockappev(ev) {
  
    ev.preventDefault();
    var appname=this.getAttribute('appname');
    var appid=this.getAttribute('appid');
    blockapp(appid,appname);
  } // END blockappev function

  function fbpshowblocked() {
    var showhidelink=document.getElementById('fbpshowblockedlink');
    var showorhidetext=showhidelink.textContent;
    
    var showhidelinkx=document.getElementById('fbpshowblockedxlink');
    var showorhidetextx=showhidelinkx.textContent;
    
    var blockedmsgs=document.getElementsByClassName('fbpblocked');
    
    if (showorhidetext=='Show') {
      if(!blockedmsgs.length)
        return;
      showhidelink.innerHTML='Hide';
      showhidelink.title='Hide Application Messages';
      showhidelinkx.innerHTML='Show';
      showhidelinkx.title='Show Extras (Friend/Group/Fan/Event Messages)';
      fbpstyle.textContent=fbpStyleApps;
    }
    else  {
      showhidelink.innerHTML='Show';
      showhidelink.title='Show Application Messages';
      fbpstyle.textContent=fbpStyleNormal;
    }    
    
    for(i=0; i<blockedmsgs.length; i++) {
      dspBlockLink(blockedmsgs[i]);      
    }

    blockedmsgs=null;
        
  } // END fbpshowblocked function

  function fbpshowblockedx() {
    var showhidelinkx=document.getElementById('fbpshowblockedxlink');
    var showorhidetextx=showhidelinkx.textContent;

    var showhidelink=document.getElementById('fbpshowblockedlink');
    var showorhidetext=showhidelink.textContent;
    
    var blockedmsgs=document.getElementsByClassName('fbpblockedx'); 
    
    if (showorhidetextx=='Show') {
      if(!blockedmsgs.length)
        return;
      showhidelinkx.innerHTML='Hide';
      showhidelinkx.title='Hide Extras (Friend/Group/Fan/Event Messages)';
      showhidelink.innerHTML='Show';
      showhidelink.title='Show Application Messages';
      fbpstyle.textContent=fbpStyleExtras;
    }
    else  {
      showhidelinkx.innerHTML='Show';
      showhidelinkx.title='Show Extras (Friend/Group/Fan/Event Messages)';
      fbpstyle.textContent=fbpStyleNormal;
    }
    
    blockedmsgs=null;  
 
  } // END fbpshowblockedx
  
  function fbpshowblockedev(ev) {
    ev.preventDefault();
    fbpshowblocked();
  }
  
  function fbpshowblockedxev(ev) {
    ev.preventDefault();
    fbpshowblockedx();
  }
  
  function dspBlockLink(node) {
    if (node.getElementsByClassName('blocklink').length)
      return;
    var footernode=node.getElementsByClassName('UIActionLinks')[0];
  
    story_meta=node.getAttribute('data-ft');
    story_type=story_meta.match(/\{\"sty\":\"(\d+)\"/)[1];
    if (story_type!=APPLICATION_STORY) {
      return
    }
    appid=story_meta.match(/\"app_id\":\"(\d+)\"/)[1];
      
    try {
      appname=footernode.getElementsByClassName("GenericStory_BottomAttribution")[0].getElementsByTagName('a')[0].textContent;
    } catch (e) {
        flinks=footernode.getElementsByTagName('a');
        appname=flinks[flinks.length-1].textContent;
        flinks=null;
    }
    
    var blinkinsertpoint=node.getElementsByClassName('UIStory_Hide')[0];
    
    if(typeof blinkinsertpoint == 'undefined')
      return;
      
    var blinkspan=document.createElement('span');
         
    blinkspan.style.cssFloat='right';
    blinkspan.style.margin='2px';
    blinkspan.className='UIStory_Hide';
                   
    blinkspan.innerHTML="<a title='FBP - Block this application' appid='" + appid + "' appname='" + appname.replace('"','','g').replace("'",'','g') + "' class='blocklink' href='javascript:;'><b>Block App</b></a>";
    blinkspan.getElementsByTagName('a')[0].addEventListener("click", blockappev, false);
    blinkinsertpoint.parentNode.insertBefore(blinkspan, blinkinsertpoint); //(insert before pattern)
  } // END dspBlockLink function
  
  function cleartheshizzle(thenode) {
    //unsafeWindow.console.log('clearing the shizzle a');
    if(!document.getElementById('fbpblockcount')) {
      crappyappmsgcounter=0;
      
      fbpstyle.textContent=fbpStyleNormal;

      //var insertpoint = document.getElementById('home_sidebar');
      var insertpoint = document.getElementsByClassName('UIIntentionalStream_Top_Icon')[0];
      
      if(insertpoint && insertpoint.firstChild) {
        var fbpurityinfo=document.createElement('div');
        //fbpurityinfo.setAttribute('class','UIOneOff_Container');
        fbpurityinfo.style.marginBottom='12px';
        fbpurityinfo.style.cssFloat='right';
        fbpurityinfo.style.background='whitesmoke';
        fbpurityinfo.style.display='inline';
        fbpurityinfo.innerHTML='&nbsp;<a title="' + fbpVersion + ' - Facebook Purity HomePage - Check for News + Updates - Leave Feedback" href="http://bit.ly/fbpure">FB Purity</a> hid: &nbsp;<span id="fbpblockcount">0</span> <abbr title="Application Messages">app</abbr> [ <a title="Show Application Messages" id="fbpshowblockedlink" href="javascript:;">Show</a> ] &nbsp;<span id="fbpblockxcount">0</span> <abbr title="Friend/Group/Fan/Event Messages">extra</abbr> [ <a title="Show Friend/Group/Fan/Event Messages" id="fbpshowblockedxlink" href="javascript:;">Show</a> ]&nbsp;';
        insertpoint.insertBefore(fbpurityinfo, insertpoint.firstChild);
        document.getElementById('fbpshowblockedlink').addEventListener("click", fbpshowblockedev, false);
        document.getElementById('fbpshowblockedxlink').addEventListener("click", fbpshowblockedxev, false);
        fpbblockcountspan=document.getElementById('fbpblockcount');
        fpbblockxcountspan=document.getElementById('fbpblockxcount');
      }
      
      if ((!suggestionsbox) && document.getElementById('pymk_hp_box')) 
        document.getElementById('pymk_hp_box').style.display='none';

    }
        
    var storynodes=thenode.getElementsByClassName('GenericStory');
    var singlestory=0;
    if (!storynodes[0])
      if(thenode.getAttribute && thenode.getAttribute('class') && thenode.getAttribute('class').match('GenericStory ')) {       
        storynodes[0]=thenode;
        singlestory=1;
      }
      else {
         //unsafeWindow.console.log('no generic');
        return;
      }

    //unsafeWindow.console.log('clearing the shizzle b');
    var blockit, blockitx; 
    var story_meta, story_type, fbpAppId ;
    
    if(singlestory)
      storynodeslength=1;
    else  
      storynodeslength=storynodes.length;
      
    for(i=0;i<storynodeslength;i++) {
      blockit=0;
      
      story_meta=storynodes[i].getAttribute('data-ft');
      story_type=story_meta.match(/\{\"sty\":\"(\d+)\"/)[1];
            
      if (story_type==APPLICATION_STORY) {
        // its an application message
        blockit=1;
        //unsafeWindow.console.log('app found : ');
        fbpAppId=story_meta.match(/\"app_id\":\"(\d+)\"\}/)[1];  
        //{"sty":"237","actrs":"100000397940964","pub_time":"1257708183","fbid":"220320497587","app_id":"145585441932"}        
        //unsafeWindow.console.log(fbpAppId);
        if(arrwhitelist2[fbpAppId])
          // its in the whitelist so dont block it
          blockit=0;
        if(blockit) {
          // mark app as blocked
          storynodes[i].setAttribute('class',storynodes[i].getAttribute('class')+' fbpblocked');
          dspBlockLink(storynodes[i]);
        } 
        else {
          // mark whitelisted app node as "normal"
          storynodes[i].setAttribute('class',storynodes[i].getAttribute('class')+' fbpnormal');
        }
        
      } 
      else if (arr_extras_blacklist2[story_type]) {
        // its an "extra" message ( became friends/became fan/joined group/attend events )
        storynodes[i].setAttribute('class',storynodes[i].getAttribute('class')+' fbpblockedx');     
      }
      else if ((story_type==PHOTO_STORY) || (story_type==TAGGED_IN_ALBUM_STORY )) {
        // its a "photo" message ( lets check if its application spam )
        if (storynodes[i].innerHTML.indexOf('apps.facebook.com')!=-1)
          storynodes[i].setAttribute('class',storynodes[i].getAttribute('class')+' fbpblocked');     
        else
          storynodes[i].setAttribute('class',storynodes[i].getAttribute('class')+' fbpnormal');
      }
      else {
        //its a "normal" story node so mark it as such
        storynodes[i].setAttribute('class',storynodes[i].getAttribute('class')+' fbpnormal');
      }
      
      updateblockedcount();
      
    }
    
    storynodes=null;
    
  }// END cleartheshizzle function
  
  if(document.getElementById('home_stream') )
    cleartheshizzle(document);
    
 }// end anon function
, true); // end window load event listener