// ----- 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

__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',
'
',
'
',
'
',
'