back to script
// -------- Odnoklassniki ---------
// @url http://www.odnoklassniki.ru
// @include http://*.odnoklassniki.ru*
// @name Odnoklassniki
// @desc Tray notifications of new messages.
// version 0.4

/**
 * @author Simon Hanukaev
 * Revision History:
 * version 0.4
 * 	- NEW:	iframe preloading changed to xmlHttpRequest.
 * version 0.3.1.2
 * 	- NEW:	Added icon blinking when new message arives.
 * version 0.3.1
 * 	- BUG:	IE "Start Navigation" sound, the refresh of the iframe is changed to XHR.
 * 	- BUG:	IE "Start Navigation" on the odnoklassniki refresh (not related to the extension
 * 			itself), there is a problem in gwt.js file.
 * 	- NEW:	Reloading iframe only when new event found on the page itself (Thanks to new AJAX
 * 			interface of the odnoklassniki.ru)
 * 	- NEW:	New notification design with scrollable messages.  
 * 	- NEW:	Added configuration parameter: isNotifyEnabled
 * version 0.3
 * 	- BUG: IE "Start Navigation" sound.
 * 	- NEW: Context Menu Items. 
 * version 0.1
 * 	- first release
 */

new function() {

//SSB.console.init("debug","iframe");
//SSB.console.init("debug","window");

//var reloadInterval = 60;
var reloadInterval = 30;
var hiddenContainer = null;
var isEventSidebarShown = false;
var currIcon = 0;
var bubblesTrckr = null;

SSB.setIcon("http://www.odnoklassniki.ru/favicon.ico");

// Check if user has logged in. The checking done by parsing URL.
var URL = window.location.href;
SSB.console.debug("The URL is: "+URL);
var isLoggedIn = /odnoklassniki\.ru\/dk/.test(URL);
isLoggedIn = isLoggedIn && !( /dk?st\.cmd=registration/.test(URL) );
if (!isLoggedIn) {
	SSB.console.debug("Didn't found 'dk?st.cmd=' or found 'dk?st.cmd=registration'.");
	SSB.console.debug("Exiting");
	return;
}

// The prefix of the domain changes every time user logs in. To prevent restriction
// issues, need to remember the prefix and use it after for message checking.
var wwwPrefix = URL.match(/http:\/\/([\d\w]+)\.odnoklassniki/)[1];
SSB.console.debug("The www prefix is: "+wwwPrefix);

// Initialization of configurable parameters prefsPane.
// isNotifyEnabled is user configurable parameter, indicates if the user want to be
// notified on new emails arrival.
SSB.prefs.pane.init([{type:'bool', name:'isNotifyEnabled', defVal:true, title:'Notify when new message arrives'}]);
SSB.contextMenu.add("My Page","http://"+wwwPrefix+".odnoklassniki.ru/dk?st.cmd=userMain");
SSB.contextMenu.add("My Friends","http://"+wwwPrefix+".odnoklassniki.ru/dk?st.cmd=userFriend");
SSB.contextMenu.add("My Messages","http://"+wwwPrefix+".odnoklassniki.ru/dk?st.cmd=userMessageIncomingNew");
SSB.contextMenu.add("People Search","http://"+wwwPrefix+".odnoklassniki.ru/dk?st.cmd=userSearchFriends");
var incomingMessagesUrl = "http://"+wwwPrefix+".odnoklassniki.ru/dk?st.cmd=userMessageIncomingNew";
SSB.console.debug("The incomingMessagesUrl is: "+incomingMessagesUrl);
var uploadingImageUrl = "http://"+wwwPrefix+".odnoklassniki.ru/dk?st.cmd=userAddPhoto";
SSB.console.debug("The uploadingImageUrl is: "+uploadingImageUrl);

createHiddenContainer();
createBubblesTrckr();

window.setInterval(checkForEvent, reloadInterval*1000);

// Uncomment to get messages immediatly without checking eventsidebar (runs once).
// sendXmlHttpRequest();

function checkForEvent() {
	SSB.console.debug("Entered checkForEvent()");
	var event_sidebar = document.getElementById("event_sidebar");
	SSB.console.debug("event_sidebar = "+event_sidebar);
	if (event_sidebar) {
		SSB.console.debug("There is event_sidebar");
		if (!isEventSidebarShown) {
			isEventSidebarShown = true;
			blinkIcon();
		}
		sendXmlHttpRequest();
	} else {
		SSB.console.debug("There is no event_sidebar");
		isEventSidebarShown = false;
	}
}

function blinkIcon() {
	SSB.console.debug("currIcon: "+currIcon);
	if (currIcon == 0) {
		currIcon = 1;
		SSB.setIcon("http://www.odnoklassniki.ru/favicon.ico");
	}
	else {
		currIcon = 0;
		SSB.setIcon("http://bubbleshq.com/client/icons/odnoklassniki_new_msg.ico");
	}
	
	if (isEventSidebarShown || currIcon == 1)
		window.setTimeout(blinkIcon,2000);
}

function sendXmlHttpRequest() {
	try {
		SSB.console.debug("sendXmlHttpRequest()");
		var xmlHttp = getXmlHttpObject();
		xmlHttp.onreadystatechange = function() {
			if (xmlHttp.readyState == 4) {
				SSB.console.debug("xmlHttp.readyState == 4");
				var responseHtml = xmlHttp.responseText;
				var messagesTableOuterHtml = responseHtml.match(/\]*?name="messagesForm"[\s\S]*?(\[\s\S]*?)*\<\/table\>)[\s\S]*?\<\/form\>/);
				if (messagesTableOuterHtml) {
					SSB.console.debug("Found  element that contains all the messages.");
					hiddenContainer.innerHTML =  messagesTableOuterHtml[1];
					Messages.changeSmiles('div');
					try {
						checkForNewMessages();
					} catch(e) {
						SSB.console.error("Bubble script error in checkForNewMessages(): " + e.name + " - " + e.message);
					}
				} else {
					SSB.console.error("Did not found 
element!"); } } } xmlHttp.open("GET",incomingMessagesUrl+getRandomToken(),true); xmlHttp.send(null); } catch(e) { SSB.console.error("Bubble script error in sendXmlHttpRequest(): " + e.name + " - " + e.message); } } function checkForNewMessages() { SSB.console.debug("checkForNewMessages()"); var messagesTable = hiddenContainer.getElementsByTagName("table")[0]; SSB.console.debug("messagesTable className = "+messagesTable.className); var tableBody = messagesTable.tBodies[0]; var tableRows = tableBody.rows; var numOfMessages = tableRows.length; SSB.console.debug("numOfMessages = "+numOfMessages); var lastMessageId = SSB.prefs.getValue("lastMessageId"); // --- uncomment for dev // var lastMessageId = 0; SSB.console.debug("Last message id = "+lastMessageId); var isFinished = false; var messageId = null; var userId = null; var inputValue = null; var messagesHtml = ""; var messageCount = 0; var msgAreaInnerHTML = null; for (var i = 0; i < numOfMessages && !isFinished; i++) { SSB.console.debug("Parsing "+(i+1)+" message...") inputValue = tableRows[i].cells[0].getElementsByTagName("input")[0].value; var inputValueMatch = inputValue.match(/(\d+):(\d+)/); messageId = inputValueMatch[1]; userId = inputValueMatch[2]; SSB.console.debug("Got message with id: "+messageId); if (i == 0) { SSB.console.debug("Setting lastMessageId to: "+messageId); SSB.prefs.setValue("lastMessageId",messageId); if ( !isNotifyEnabled() ) { lastMessageId = messageId; isFinished = true; } } // If the extension running for the first time if (lastMessageId == null) { lastMessageId = 0; } if (messageId > lastMessageId) { messageCount++; var senderName = trimWhiteSpaces(tableRows[i].cells[1].innerText); SSB.console.debug("From: " + senderName); msgAreaInnerHTML = getElementsByClassName("msg-area", tableRows[i].cells[2])[0].innerHTML; //SSB.console.debug("Message inner html: "+msgAreaInnerHTML+"\n"); var messageHtml = getStringFromFunction(messageHTML); messageHtml = messageHtml.replace(/__senderName__/, senderName); messageHtml = messageHtml.replace(/__userId__/, userId); messageHtml = messageHtml.replace(/__messageBody__/, msgAreaInnerHTML); messageHtml = messageHtml.replace(/__leftStyle__/, (i != 0) ? "" : 'style="left:auto;"'); messageHtml = messageHtml.replace(/__messageNumber__/, (i + 1)); //SSB.console.debug(""); messagesHtml += messageHtml; } else { isFinished = true; } } SSB.console.debug("messageCount = "+messageCount); if (messageCount > 0) { var html = getStringFromFunction(notificationHTML); var url = "http://bubbleshq.com/client/odnoklassnikiNotification/"; html = html.replace(/__numOfMessages__/g, messageCount); html = html.replace(/__URL__/g, url); html = html.replace(/__MESSAGES__/, messagesHtml); //SSB.console.debug(html); if (bubblesTrckr) { try { bubblesTrckr._trackEvent("Notifications", "Odnoklassniki"); } catch(e) {}; } SSB.notify(html, 295, 117, 10); } } window.bubblesComposeNewMessage = function(userId) { var url = "/dk?st.cmd=userMessageCreateP&st.friendId="+userId; openWinCentered(url, "msg" + userId, 520, 650, 'location=no,status=no,toolbar=no,menubar=no,resizable=yes,scrollbars=yes'); } /** * Get the isNotifyEnabled preference value, and return it. * If this value was not set before, then it will be the default one defined in * SSB.prefs.pane.addPrefs. */ function isNotifyEnabled() { // User configurable parameter, if he want to be notified of new emails. var isNotifyEnabled = SSB.prefs.getValue("isNotifyEnabled"); // isNotifyEnabled can't be null as it set with SSB.prefs.pane.addPrefs and uses // defVal:true; SSB.console.debug("Get isNotifyEnabled="+isNotifyEnabled); // For backward capability, in previous versions of bubbles the default value was not set // automatically. if (isNotifyEnabled === null) { isNotifyEnabled = true; SSB.prefs.setValue("isNotifyEnabled", isNotifyEnabled); } return isNotifyEnabled; } /** * Function that receives an element node as a parameter and returns first child element node of that element. * If the element does not have child elements node the method returns null. */ function firstChildElement(element) { for (var i = 0; i < element.childNodes.length; i++) if ( element.childNodes[i].nodeType == 1 ) return element.childNodes[i]; return null; } /** * Gets elements by their class names * @param {String} className The class name to search. Can be complex like "title bold", then it will * find all elements that have "title" and "bold" classes. Can also be a regular expression. * @param {String} parentElement The ancestor element in which to search for elements. * @return {Array} elements Array of element nodes that matched the class name. */ function getElementsByClassName(aClassName, parentElement) { var className = aClassName.replace(/^\s+/, "").replace(/\s+$/, ""); if (className == "") return null; var classNamesArray = className.split(/\s+/); var elements = new Array(); if (!parentElement) parentElement = document; try { elements = traverseSiblings(parentElement); } catch(e) { SSB.console.error("Bubble script error in traverseSiblings(): " + e.name + " - " + e.message); } finally { return elements; } /** * Private recursy function for traversing node tree. */ function traverseSiblings(parentElement) { var elements = new Array(); var child = null; var hasClass = null; var regexp = null; for (var i = 0; i < parentElement.childNodes.length; i++) { child = parentElement.childNodes[i]; if ( child.nodeType == 1 ) { hasClass = true; for (var j = 0; j < classNamesArray.length && hasClass; j++) { regexp = new RegExp("\\b"+classNamesArray[j]+"\\b"); if (!regexp.test(child.className)) { hasClass = false; } } if (hasClass) { elements.push(child); } elements = elements.concat( traverseSiblings(child) ); } } return elements; } } function trimWhiteSpaces(string) { return string.replace(/^\s+/, "").replace(/\s+$/, ""); } function createHiddenContainer() { hiddenContainer = document.createElement("div"); hiddenContainer.style.position = "absolute"; hiddenContainer.style.top = "-10000px"; hiddenContainer.style.right = "0px"; hiddenContainer.style.zIndex = "1000"; hiddenContainer.style.width = "300px"; hiddenContainer.style.height = "200px"; hiddenContainer.style.backgroundColor = "#FFFFFF"; hiddenContainer.style.overflow = "scroll"; hiddenContainer.className = "cntin"; document.body.appendChild(hiddenContainer); } function createBubblesTrckr() { var script = document.createElement("script"); script.src = "http://www.google-analytics.com/ga.js"; script.type = "text/javascript"; document.body.appendChild(script); var tries = 0; // Give some number of tries if script did not loaded. function initTrckr() { try { bubblesTrckr = _gat._getTracker("UA-221829-10"); bubblesTrckr._trackPageview("/notifications/odnoklassniki"); } catch (e) { tries++; if (tries < 10) { window.setTimeout(initTrckr, 200); } } } initTrckr(); } function getRandomToken() { var randNum = parseInt(Math.random()*10000); return "&tkn="+randNum; } /* * Get xmlHttp Object. */ function getXmlHttpObject() { var xmlHttp; try { // Internet Explorer 6.0+ xmlHttp=new ActiveXObject("Msxml2.XMLHTTP"); } catch (e) { // Internet Explorer 5.5+ xmlHttp=new ActiveXObject("Microsoft.XMLHTTP"); } return xmlHttp; } /* * Gets function reference and returns string of first matched text between commented * block just like this one. */ function getStringFromFunction(fnc) { var string = fnc.toString(); return string.match(/\/\*([\s\S]*)\*\//)[1]; } /** * HTML code for the notification. * All parametes that is replaced at run time is of __parameterName__ form. */ function notificationHTML() {/* Odnoklassniki.ru Notification
1/__numOfMessages__
previous next
close
__MESSAGES__
*/} /** * HTML code for every message received, the first message should have style="left:auto;" * All parametes that is replaced at run time is of __parameterName__ form. */ function messageHTML() {/*
__senderName__ [ответить]
__messageBody__
*/} // ------------------------------------------- // ---------- Drag and Drop Feature ---------- // ------------------------------------------- /* * SSB.dragDrop.ondrop * This handler invoked when user drops image file into Bubbles. * This file is then uploaded to the odnoklassniki server and redirects the user * to the confirmation and photo commenting page. */ SSB.dragDrop.ondrop = function() { SSB.console.debug("SSB.dragDrop.ondrop()"); var boundary = '----------------{08F41DE12407479f988D0752953C36773D3R}--'; var postBodyBinaryData = ''; var data = ''; var postUrl = ''; var returnUrl = ''; var fileData = SSB.dragDrop.getFileData(0) var fileName = SSB.dragDrop.getFileName(0); SSB.console.debug("Dropped file = "+fileName); // Build POST body binary data data = "--"+boundary+"\r\n"; data += 'Content-Disposition: form-data; name="st.filename"; filename="'+fileName+'"\r\n'; // TODO make feature in client that extracts the file type automatically data += "Content-Type: image/jpeg\r\n\r\n"; postBodyBinaryData = window.external.mergeData(postBodyBinaryData, data); postBodyBinaryData = window.external.mergeData(postBodyBinaryData, fileData); postBodyBinaryData = window.external.mergeData(postBodyBinaryData, "\r\n"); postBodyBinaryData = window.external.mergeData(postBodyBinaryData, "--"+boundary+"--\r\n"); // extract image upload url and with it all the photo unique parameters. getImageUploadingPostUrl(); function getImageUploadingPostUrl() { try { SSB.console.debug("getImageUploadingPostUrl()"); var xmlHttp = getXmlHttpObject(); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4) { SSB.console.debug("getImageUploadingPostUrl() xmlHttp.readyState == 4"); var responseHtml = xmlHttp.responseText; // Find element, put it in formActionUrl var form = responseHtml.match(/\]*?enctype="multipart\/form-data"[^\>]*?\>/)[0]; SSB.console.debug("upload form = "+form); // Get actionUrl from action attribute of found form. var formActionUrl = form.match(/action=['"](.*?)['"]/); if (formActionUrl) { postUrl = formActionUrl[1]; SSB.console.debug("postURL = "+postUrl); returnUrl = decodeURIComponent(postUrl); SSB.console.debug("encoded postURL = "+returnUrl); returnUrl = returnUrl.replace(/&/g, "&"); returnUrl = returnUrl.match(/returnUrl=(.*?)&returnErrorUrl/)[1]; SSB.console.debug("returnUrl = "+returnUrl); uploadImage(); } } } xmlHttp.open("GET",uploadingImageUrl+getRandomToken(),true); xmlHttp.send(null); } catch(e) { SSB.console.error("Bubble script error in getImageUploadingPostUrl(): " + e.name + " - " + e.message); } } function uploadImage() { try { SSB.console.debug("uploadImage()"); // var iframe = document.createElement("iframe"); // iframe.src = "http://"+wwwPrefix+".odnoklassniki.ru/"; // iframe.style.position = "absolute"; // iframe.style.top = "0000px"; // iframe.style.right = "0px"; // iframe.style.zIndex = "1000"; // iframe.style.width = "400px"; // iframe.style.height = "300px"; // iframe.style.backgroundColor = "#FFFFFF"; // iframe.style.overflow = "scroll"; // document.body.appendChild(iframe); // iframe.contentWindow.xmlHttp = getXmlHttpObject(); // var xmlHttp = iframe.contentWindow.xmlHttp; var xmlHttp = getXmlHttpObject(); xmlHttp.open("POST", postUrl, true); xmlHttp.setRequestHeader("Content-Type","multipart/form-data; boundary="+boundary); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4) { SSB.console.debug("uploadImage() xmlHttp.readyState == 4"); window.location.href = returnUrl; } } xmlHttp.send(postBodyBinaryData); } catch (e) { SSB.console.error("Bubble script error in uploadImage(): " + e.name + " - " + e.message); } } } }