// ----- Gmail Client ------ // @url https://mail.google.com/mail/ // @name Gmail // @desc Notifies about new/unread emails. // version 0.7 // - added drag&drop feature // version 0.6 beta // - added ensure visible when clicking anywhere on the notification box, except close and // next and prev buttons. // version 0.5 beta // anonymous function that wraps whole script to prevent naming conflicts new function() { //SSB.console.init("debug","window"); var win = window.top; // refreshTimeMS - refresh time in milliseconds to check for new emails var refreshTimeMS = 10000; var bubblesTrckr = null; var elements = new Elements(); window.getDocumentHandlerForPrefsPane = elements.getFrameDocument; SSB.prefs.pane.documentHandler = "window.getDocumentHandlerForPrefsPane()"; createBubblesTrckr(); if (window.self != window.top) SSB.console.info("win is not top most windows. May cause problems!"); checkTitleChange(); function checkTitleChange() { try { /* do this every refreshTimeMS seconds */ win.setTimeout(checkTitleChange, refreshTimeMS); var title = win.document.title; SSB.console.debug("title=" + title); /* make sure we've got a title we can work with */ if (title.search('Inbox') == -1) return; /* get current number of unread mails */ var result = title.match(/(Inbox \()(\d+)(\))/); var curUnread = (result && result.length == 4) ? parseInt(result[2]) : 0; /* get previous number of unread mails */ var prevUnread = SSB.prefs.getValue('num_unread'); SSB.console.debug("Got prevUnread = "+prevUnread); /* immediately update with new value */ SSB.prefs.setValue('num_unread', curUnread+''); if (prevUnread == null) prevUnread = 0; else prevUnread = parseInt(prevUnread); /* if the number grew (from 0 to 1, or from 3 to 899), tell the user */ if (curUnread > prevUnread) { SSB.console.debug("curUnread = " + curUnread + " > " + prevUnread + " = prevUnread"); if (bubblesTrckr) { try { bubblesTrckr._trackEvent("Notifications", "Gmail"); } catch(e) {}; } var newEmails = parseUnreadEmailsData(curUnread); var url = "http://bubbleshq.com/client/gmailNotification/"; var emails = ""; for (var i=0; i < newEmails.length; i++) { var email = getStringFromFunction(emailHTML); email = email.replace(/__FROM__/, newEmails[i].from); email = email.replace(/__SUBJECT__/, newEmails[i].subject); email = email.replace(/__MSG__/, newEmails[i].firstLine); email = email.replace(/__emailNumber__/, (i+1) ); email = email.replace(/__leftStyle__/, (i!=0)?"left:-1000px;":""); emails += email; } var html = getStringFromFunction(notificationHTML); // html = html.replace(/__gmailURL__/, "http://mail.google.com/mail/"); html = html.replace(/__numOfEmails__/g, newEmails.length); html = html.replace(/__EMAILS__/, emails); html = html.replace(/__URL__/g, url); SSB.notify(html, 295, 117, 10); } /* update icon (icons are cached so we can give direct url) */ var iconURL = "http://www.3d3r.com/bubbles/icons/gmail_" + (curUnread != 0 ? "unread" : "read") + ".ico"; SSB.console.debug("The icon URL is: " + iconURL); SSB.setIcon(iconURL); } catch(e) { SSB.console.error("checkTitleChange: " + e.name + ": " + e.message); } } /* * Parse email header data (from, subject, time, etc.) from emails table. * Uses curUnread value to stop iteration when all unread emails are found. */ function parseUnreadEmailsData(curUnread) { try { SSB.console.debug("parseUnreadEmailsData(" + curUnread + ")..."); // get iFrame that is used for inxob view var iFrameDoc = elements.getCurrIFrameDocument(); if (iFrameDoc == null) throwError("Parsing Error","didn't get the iFrame!"); SSB.console.debug("Current iFrame's id is: " + iFrameDoc.parentWindow.frameElement.id); // get emails Table that hold all the emails header data // Getting table with id is not stable, because it differs with browsers and // windows version. // var emailsTable = iFrameDoc.getElementById(elements.emailsTableId); var emailsTable = null; var tables = iFrameDoc.getElementsByTagName("table"); SSB.console.debug("Found " + tables.length + "tables."); var numOfColumns = null; SSB.console.debug("Starting to iterate to find emails table."); for (var i = 0; i < tables.length; i++) { numOfColumns = tables[i].getElementsByTagName("col").length; if (numOfColumns == 7) { emailsTable = tables[i]; break; } } if (emailsTable == null) throwError("parsingError","didn't find emailsTable!"); SSB.console.debug("Got emails table with id: " + emailsTable.id); // get emails table rows var emails = emailsTable.getElementsByTagName("tr"); if (emails == null) throwError("parsingError","didn't find emails!"); SSB.console.debug("Got email table rows. Their length is: " + emails.length); var fields, from, subject, time, firstLine; var firstLineClass = elements.firstLineClass; var unansweredEmailsFound = 0; var newEmails = new Array(); // run on email rows in the table. var unreadRegexp = new RegExp(elements.unreadClass); for (i = 0; i < emails.length && unansweredEmailsFound < curUnread; i++) { email = emails[i]; // if the email is unread email // "ur" class for IE6 and "QhHSYc" class for IE7 if (unreadRegexp.test(email.className)) { SSB.console.debug("Found unanswered email. His index (from 0) is: " + i); fields = email.getElementsByTagName("td"); // next statements throwing errors if the text nodes not found // from = fields[2].getElementsByTagName(elements.fromTag)[0].firstChild.nodeValue; from = fields[2].innerText; SSB.console.debug("From: " + from); subject = fields[4].getElementsByTagName("b")[0].firstChild.nodeValue; SSB.console.debug("Subject: " + subject); firstLine = getElementsByClassName(firstLineClass, fields[4]); if (firstLine.length > 0) firstLine = firstLine[0].innerText; else firstLine = ""; SSB.console.debug("FirstLine: " + firstLine); time = fields[6].getElementsByTagName("b")[0].firstChild.nodeValue; SSB.console.debug("Time: " + time); // with all of the data the notification looks shrinky. so I will use only the from field. newEmails[unansweredEmailsFound] = new Email(from,subject,firstLine,time); unansweredEmailsFound++; } } // var delta = curUnread - maxEmailHeaders; // if (delta > 0) { // SSB.console.debug("delta = curUnread - maxEmailHeaders = " + delta); // } return newEmails; } catch(e) { SSB.console.error("parseUnreadEmailsData(): " + e.name + ": " + e.message); return newEmails; } } /** * Email object. Holds email header data. */ function Email(from,subject,firstLine,time) { this.from = from; this.subject = subject; this.firstLine = firstLine; this.time = time; } /* * function for throwing custom errors. */ function throwError(error, msg) { var err = new Object(); err.name = error; err.message = msg; throw err; } /* * Elements object holds all the differences between IE6 and IE7 of gmail DOM needed * for the script * IE6: * There is a main 3 iFrames that is switched between each other every action, or not. * So on first gmail load the main inbox is in the first iFrame, but after * sending an email for example, the inbox opens in the third iFrame. * getCurrIFrameDocument() function checks which iFrame is currently viewed by * checking it's z-index style property. * IE7: * There is 4 iframes. The main page is in the third iframe. */ function Elements() { SSB.console.debug("elementsPath()..."); var version = /MSIE ([\d\.]+)/.exec(win.navigator.appVersion)[1]; SSB.console.debug("IE version: " + version); if (version >= 7) { this.emailsTableId = "1f7a"; this.unreadClass = "QhHSYc"; this.fromTag = "span"; this.firstLineClass = "bEeVec"; } else { this.fromTag = "b"; this.emailsTableId = "tb"; this.unreadClass = "ur"; this.firstLineClass = "p"; } this.getFrameDocument = function() { if (version >=7) return win.document.frames[3].document; return win.document.frames[0].document; } this.getCurrIFrameDocument = function() { try { if (version >=7) return win.document.frames[3].document; var subIFrames = win.document.frames[0].document.getElementsByTagName("iframe"); SSB.console.debug("Iterating through subFrames. Length = " + subIFrames.length); for (i=0; i < subIFrames.length; i++) { SSB.console.debug("Checking iFrame with id: " + subIFrames[i].id); zIndex = subIFrames[i].style.zIndex; if (zIndex == 1) { SSB.console.debug("Found iframe with zIndex == 1. It is " + (i+1) + " iFrame"); return win.document.frames[0].document.frames[i].document; } } SSB.console.error("elements.getCurrIFrameDocument(): Didn't find iFrame with zIndex == 1"); return null; } catch (e) { SSB.console.error("elements.getCurrIFrameDocument():" + e.name + ": " + e.message); return null; } } } function createBubblesTrckr() { try { var script = document.createElement("script"); script.src = "http://www.google-analytics.com/ga.js"; script.type = "text/javascript"; var head = document.getElementsByTagName("head")[0]; head.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/gmail"); } catch (e) { tries++; if (tries < 10) { window.setTimeout(initTrckr, 200); } } } initTrckr(); } catch (e) {} } /* * 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() {/*