back to script
// --------- FaceBook ----------
// @url http://www.new.facebook.com
// @name Facebook
// @desc This script notifies about every new story in the Live Feed.
// version 1.9.1

/**
 * This script finds every new story (by its header text) in the
 * Lives Feed and then appends this story to the notification.
 * When all new stories are extracted, user gets notifiaction about his/her new stories.
 * The procedure of reloading the iframe called every "reloadInterval" seconds
 */

// This variable is visible to the facebook page! It stores executable scripts from
// Lives Feeds. So when you clicking on some link in notification message that needs to run
// javascript, the script is taken from here (main bubble scope) otherwise the script in
// notification message scope will fail to run.
var executableScriptForBubbles;

// anonymous function that wraps whole script to prevent naming conflicts
new function() {
//SSB.console.init("debug","iframe");
log = SSB.console.debug;

// ----- Constants -----
var FACEBOOK_COMMON_CSS_URL = "http://static.ak.fbcdn.net/rsrc.php/z5GT6/pkg/102/127996/css/common.css.pkg.php";
var FACEBOOK_FEED_CSS_URL = "http://static.ak.fbcdn.net/rsrc.php/zBKH5/pkg/102/126119/css/feed.css.pkg.php";
var CHAT_CHECKING_INTERVAL = "5";	// in seconds
var IE_VERSION = /MSIE ([\d\.]+)/.exec(navigator.userAgent)[1];
var bubblesTrckr = null;

// ----- Configuration Variables -----
var maxNotificationHegiht = 277;	// Max height of the notification message.
									// If the notification content is shorter than
									// maxNotificationHeight, then the only the needed
									// height will be used.
var reloadIntervalDefault = 120;
SSB.prefs.pane.init([{type:'string',defVal:reloadIntervalDefault,name:'reloadInterval',title:'News Feed checking interval (in seconds)'}]); 
var reloadInterval = SSB.prefs.getValue("reloadInterval");
if (reloadInterval == null) reloadInterval = reloadIntervalDefault;
SSB.prefs.setValue("reloadInterval", reloadInterval);
createBubblesTrckr();

var URL = window.location.href;
var facebookURL = URL.match(/(http:\/\/[^\/]+)\//)[1];
var bubblesBugNewsFeedContainer = null;
var doc = document;
testStylesAdded = false;

function error(err, inFunction) {
	var msg = "Error occurred in "+inFunction+"\n";
	for (each in err) {
		msg += each + ": " + err[each] + "\n";
	}
	SSB.console.error(msg);
}

var bubblesBugNewsFeedContainer = doc.createElement("div");
bubblesBugNewsFeedContainer.style.position = "absolute";
bubblesBugNewsFeedContainer.style.backgroundColor = "#FFFFFF";
bubblesBugNewsFeedContainer.style.top = "-1000px";
bubblesBugNewsFeedContainer.style.left = "-1000px";
bubblesBugNewsFeedContainer.style.width = "222px";
bubblesBugNewsFeedContainer.style.height = "277px";
bubblesBugNewsFeedContainer.style.overflow = "hidden";
bubblesBugNewsFeedContainer.id = "bubblesBugNewsFeedContainer";
doc.body.appendChild(bubblesBugNewsFeedContainer);
					
var testDiv = doc.createElement("div");
testDiv.style.backgroundColor = "#FFFFFF";
testDiv.style.position = "absolute";
testDiv.style.top = "-10000px";
testDiv.style.left = "-10000px";
testDiv.style.zIndex = "100";
doc.body.appendChild(testDiv);

sendAjaxFeedRequest();
if (IE_VERSION >= 7) {
	//window.setInterval(checkChat,CHAT_CHECKING_INTERVAL);	
}

function sendAjaxFeedRequest() {
	try {
		log("Entered sendAjaxFeedRequest()");
		var xmlHttp = getXmlHttpObject();
		xmlHttp.onreadystatechange = function() {
			// in IE readyState can be: uninitialized/loading/loaded/interactive/complete
			// for xmlHttpRequest it is 0/1/2/3/4
			if (xmlHttp.readyState == 4) {
				log("xmlHttp.readyState == 4");
				try {
					var JSONString = xmlHttp.responseText.match(/\{[\s\S]*/)[0];
					var JSON = bubblesJSON.parse(JSONString)
					log("Parsed received JSON");
					bubblesBugNewsFeedContainer.innerHTML = JSON.payload.html;		
					checkNewsFeed();
				} catch(e) {
					error(e,"checkNewsFeed()");
				}
			}
		}
		var url = facebookURL+"/ajax/feed.php?tab=2";
		xmlHttp.open("POST",url,true);
		xmlHttp.send(null);
		log('Sent XmlHttpRequest to the '+url);
	} catch(e) {
		error(e,"sendAjaxFeedRequest()");
	}
	reloadInterval = SSB.prefs.getValue("reloadInterval");
	window.setTimeout(sendAjaxFeedRequest, reloadInterval*1000);
}

/**
 * Check for new feeds.
 */
function checkNewsFeed() {
	log('Entered checkNewsFeed()');
	
	var lastHeader = SSB.prefs.getValue("lastHeader");
	// If there was no lastHeader saved in prefs (Facebook bubbles runing for the
	// first time) then lastHeader will be null, and notification will include all
	// stories.

	// Uncomment to simulate constant notification
	//lastHeader = null;
	log("lastHeader = '" + lastHeader +"'");
	
	var storiesContainer = firstChildElement(bubblesBugNewsFeedContainer)
	
	var headerTitleWrapper;
	var currentHeader;
	var isLastHeaderSet = false;
	
	var numberOfNewStories = 0;
	var currentElement = firstChildElement(storiesContainer);
	var firstElementToAdd = currentElement;
	var lastAddedElement = null;
	var oneLinerCluster = null;
	var docFragment = doc.createElement("div");
	var currentFragment = docFragment;
	
	while (currentElement) {
		log("start of while: currentElement tagName = "+currentElement.tagName+" currentElement className = "+currentElement.className+" id = "+currentElement.id);
		
		currentHeader = null;
		// Try to get headerTitleWrapper of the current element
		// Check if currentElement has class="feed_item", if is then process the
		// currentElement.
		if (hasClassName(currentElement, "feed_item")) {
			// Get header of the story
			headerTitleWrapper = getElementsByClassName("header_title_wrapper",currentElement);
			// Check that headerTitleWrapper is found.
			if (headerTitleWrapper.length > 0) {
				headerTitleWrapper = headerTitleWrapper[0];
				var status_body = getElementsByClassName("status_body",headerTitleWrapper);
				if (status_body.length > 0) {
					currentHeader = status_body[0].innerText;
				} else {
					currentHeader = headerTitleWrapper.innerText;
				}
				log("-currentHeader = "+currentHeader);				
			}			
		}
		
		if (currentHeader !== null) {
			
			// If this is first iteration:
			if (!isLastHeaderSet) {
				// Set the lastHeader value of SSB.prefs to the first story header
				SSB.prefs.setValue("lastHeader", currentHeader);
				log("-SSB.prefs.setValue - lastHeader = "+currentHeader);
				isLastHeaderSet = true;
				
				// If there was no lastHeader saved in prefs (Facebook bubbles runing for the first time)
				// then do not show the notification; 
			//	if (lastHeader == null)
			//		lastHeader = currentHeader;
				
				// If last story is visible (The user is on home page and sees the last story)
				// then do not show the notification.
				if ( isLastStoryVisible(currentHeader) ) {
					log("-isLastStoryVisible(currentHeader) = true");
					lastHeader = currentHeader;
					log("-lastHeader = currentHeader");
				}
			}

			// If currentHeader not equal to last saved header, then add this story
			// to the notification.
			if (currentHeader != lastHeader) {
				numberOfNewStories++;
				do {
					log("--currentHeader != lastHeader");
					
					if (lastAddedElement != null) {
						log("--lastAddedElement = "+lastAddedElement.className);
						var elementToAdd = nextSiblingElement(lastAddedElement);
						log("--elementToAdd = "+elementToAdd.className);
					}
					else {
						log("--lastAddedElement == null");
						var elementToAdd = firstElementToAdd;
						log("--elementToAdd = "+elementToAdd.className);
					}
					
					if ( !hasClassName(elementToAdd, "social_ad") ) {
						if (elementToAdd == oneLinerCluster) {
							log("---elementToAdd isCluster! = "+elementToAdd.className);
							var clonedElementToAdd = elementToAdd.cloneNode(false);
							currentFragment = currentFragment.appendChild(clonedElementToAdd);
							elementToAdd = firstChildElement(oneLinerCluster);
							log("---elementToAdd first child of Cluster = "+elementToAdd.className);
						}

						var clonedElementToAdd = elementToAdd.cloneNode(true);
						currentFragment.appendChild(clonedElementToAdd);
					} 
					else { log("---elementToAdd (is ad) = "+elementToAdd.className); }
					
					lastAddedElement = elementToAdd;
					log("--lastAddedElement = "+elementToAdd.className+" id = "+elementToAdd.id);
				} while (lastAddedElement != currentElement);
				currentElement = nextSiblingElement(currentElement);
			} else {
				log("--currentHeader == lastHeader");
				currentElement = null;
				oneLinerCluster = null;
			}
		} else {
		// If currentElement does not have class="feed_item", then check maybe it is
		// it have class="one_liner_cluster" if is, then jump to the first child of
		// one_liner_cluster, otherwise proceed to the next sibling element.
			log("-currentHeader == null");
			if (hasClassName(currentElement, "one_liner_cluster")) {
				oneLinerCluster = currentElement;
				currentElement = firstChildElement(oneLinerCluster);
			} else {
				currentElement = nextSiblingElement(currentElement);
			}
		}
		
		if (!currentElement && oneLinerCluster) {
			log("-end of cluster!");
			var elementToAdd = nextSiblingElement(lastAddedElement);
			while (elementToAdd) {
				log("--elementToAdd = "+elementToAdd.className);
				var clonedElementToAdd = elementToAdd.cloneNode(true);
				currentFragment.appendChild(clonedElementToAdd);
				elementToAdd = nextSiblingElement(elementToAdd);
			}
			currentFragment = docFragment;
			lastAddedElement = oneLinerCluster;
			log("-lastAddedElement = "+lastAddedElement.className);
			currentElement = nextSiblingElement(oneLinerCluster);
			oneLinerCluster = null;
		}	
		log("End of iteration");
	}
	log("num of childs = "+docFragment.childNodes.length);
	
	log("numberOfNewStories = "+numberOfNewStories);
	if ( numberOfNewStories > 0 ) {
		
		if (bubblesTrckr) {
			try {
				bubblesTrckr._trackEvent("Notifications", "Facebook");
			} catch(e) {};
		}
		
		var url = "http://bubbleshq.com/client/facebookNotification/";

		docFragment = removeFeedOptions(docFragment);

		var stories = docFragment.innerHTML;
		
		stories = stories.replace(/href="([^"]*)"/g,'href="JavaScript:runHREF(\'$1\');"');
		log("Replaced href elements");
		
		
		executableScriptForBubbles = new Array();
		stories = stories.replace(/onload="[^"]*"/g, "");
		stories = stories.replace(/onmousemove="[^"]*"/g, "");
		stories = stories.replace(/onmouseout="[^"]*"/g, "");
		stories = stories.replace(/onclick="([^"]*)"/g, replaceOnClick);
		function replaceOnClick(matchedSubstring, backReference, offset, searchedString) {
			var index = executableScriptForBubbles.push(backReference) - 1;
			var result = 'onclick="runJavaScript('+index+')"';
			return result;
		}
		log("Replaced onclick attributes");
		
		var userName = doc.getElementById("fb_menu_account").innerText;
		userName = userName.match(/(\w+)[\s\S]?/)[1];
		log("userName = "+userName);
		var bodyHtml = getStringFromFunction(bodyHTML);
		bodyHtml = bodyHtml.replace(/__STORIES__/, stories);
		bodyHtml = bodyHtml.replace(/__URL__/g, url);
		bodyHtml = bodyHtml.replace(/__userName__/, userName);
		bodyHtml = bodyHtml.replace(/__quantity__/, (numberOfNewStories==1)?"story":"stories");

		if (!testStylesAdded) {
			testStylesAdded = true;
			var head = doc.getElementsByTagName("head")[0];
			var newStyle = doc.createElement("style");
			head.appendChild(newStyle);
			var styleSheets = doc.styleSheets;
			var styleSheet = styleSheets(styleSheets.length - 1);
			styleSheet.cssText = getStringFromFunction(cssText);
			
			newStyle = doc.createElement("link");
			newStyle.type = "text/css";
			newStyle.rel = "stylesheet";
			newStyle.href = FACEBOOK_FEED_CSS_URL;
			head.appendChild(newStyle);
			styleSheets = doc.styleSheets;
			styleSheet = styleSheets(styleSheets.length - 1);
		}
		
		testDiv.innerHTML = bodyHtml;
		
		var testDivHeight = testDiv.clientHeight;
		if (testDivHeight > maxNotificationHegiht)
			var notificationHegiht = maxNotificationHegiht;
		else
			var notificationHegiht = testDivHeight;
			
		var finalContentHeight = notificationHegiht - 68;

		var html = getStringFromFunction(notificationHTML);
		var css = getStringFromFunction(cssText);
		css = css.replace(/__finalContentHeight__/, finalContentHeight);
		html = html.replace(/__FACEBOOK_COMMON_CSS_URL__/, FACEBOOK_COMMON_CSS_URL);
		html = html.replace(/__FACEBOOK_FEED_CSS_URL__/, FACEBOOK_FEED_CSS_URL);
		html = html.replace(/__cssText__/, css);
		html = html.replace(/__bodyHTML__/, bodyHtml);
		html = html.replace(/__facebookURL__/g, facebookURL);
		
		//log(html)
		
		SSB.notify(html, 222, notificationHegiht, 15);
	}
}

/*
 * Removes:
 * 
inside feed items *
*