// ----- 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() {/* Gmail
M
1/__numOfEmails__
previous next bubbles
close __EMAILS__
*/} /** * HTML code for every email received, all the email except the first one should have * style.left:-1000px; * All parametes that is replaced at run time is of __parameterName__ form. */ function emailHTML() {/*
__FROM__: __SUBJECT__
__MSG__
*/} /** * 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) { } 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; } } // ------------------------------------------- // ---------- Drag and Drop Feature ---------- // ------------------------------------------- function genPost() { var boundary = '----------------{08F41DE12407479f988D0752953C36773D3R}--'; var postBody = ''; var vars = { 'redir' : '?s=d', 'nvp_bu_sd' : 'Save Draft', 'to' : '', 'cc' : '', 'bcc' : '', 'subject' : '', 'body' : '' }; for (x in vars) { postBody += '--' + boundary + '\r\n'; postBody += 'Content-Disposition: form-data; '; postBody += 'name="' + x + '"\r\n\r\n'; postBody += vars[x] + '\r\n'; } for (var i=0; i', '
Uploading...
', '
', '$FILENAMES', '

', '
', '
', '
 
', '
', '
', '
', ''].join('').replace('$FILENAMES', fileNames), 200, 92, 15, false); var xhr = SSB.getXHR(); xhr.open('POST', postUrl); xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary='+post.boundary); xhr.onreadystatechange = function() { SSB.console.debug("onreadystatechange"); SSB.console.debug("xhr.readyState = "+xhr.readyState+"\n"+ "xhr.status = "+xhr.status+"\n"+ "xhr.responseHeaders = \n"+xhr.responseHeaders+"\n"+ "xhr.responseText = \n"+xhr.responseText); if (xhr.readyState == 4) { // see if we got success if (xhr.status == 200) { SSB.console.debug("Upload Successfull"); // fetch the draft-id out of the response var draftId = xhr.responseText; draftId = draftId.split('draft=')[1].split('&')[0]; SSB.console.debug(draftId); var draftUrl = document.location.protocol + '//mail.google.com/mail/#drafts/' + draftId; SSB.console.debug(draftUrl); var userAgent = navigator.userAgent; var version = /MSIE ([\d\.]+)/.exec(userAgent)[1]; SSB.console.debug("IE version: " + version); if (version >= 7) { // this works for IE7 //document.location.replace(draftUrl); notif.document.getElementById('status').innerHTML = 'Upload Successfull'; document.write(xhr.responseText); notif.eval("SSB.notification.closeIn(2)"); } else { notif.eval("SSB.notification.closeIn(0)"); SSB.notify( "" + "
" + "File uploaded sucesfully. Due to the way GMail handles" + " Internet-Explorer 6, you will need to open the new " + "email manually. Please go to 'drafts' and open it from " + "there. For the full streamlined feature, consider " + "upgrading to Internet-Explorer 7 :-)" + "
" + "", 300, 115, 15, false); } //var url = xhr.getResponseHeader('Location'); //document.location.replace(url); } else { SSB.console.debug("Upload Failed"); notif.document.getElementById('status').innerHTML = 'Upload Failed'; notif.eval("SSB.notification.closeIn(2)"); } } } xhr.onprogress = function(progress) { SSB.console.debug("onprogress"); var p = notif.document.getElementById('progressbar'); if (!p) return; p.style.width = progress + '%'; if (progress == 100) { notif.document.getElementById('status').innerHTML = 'processing... '; } // make sure we're up for at least another 15 seconds (in case of slow upload or processing) notif.eval("SSB.notification.closeIn(50)"); } xhr.send(post.body); SSB.console.debug("XHR POSTED"); } }