Movie Dip - find movies to double dip (watch back-to-back)

On the Google Movie Showtimes, select the movies you are interested in seeing to find out which ones you can see back-to-back at the same theater.

// ==UserScript==
// @name        Movie Dip - find movies to double dip (watch back-to-back)
// @namespace   driver8.net
// @description On the Google Movie Showtimes, select the movies you are interested in seeing to find out which ones you can see back-to-back at the same theater.
// @version     0.5
// @grant       GM_addStyle
// @require     https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/sprintf/0.0.7/sprintf.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.17.5/js/jquery.tablesorter.min.js
// @require     https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.min.js
// @match		*://*.google.com/movies*
// ==/UserScript==

// im not vry good @ javascript sry

var AND_BUTTS = false;
var NUM_DIPS = 2;
var FRONT_TOLERANCE = 5;
var END_TOLERANCE = 40;
var FADE_SPEED = 200;
var PRETTY = true;
var LOGGING = false;

function log(msg) {
    LOGGING && console.log(msg);
}

function Film(title, id, length, times) {
    this.title = title;
    this.id = id;
    this.len = length;
    this.selected = false;
    this.checkBox = null;
    this.div = null;

    this.times = times;
}
Film.prototype.constructor = Film;


function Theater(name, id, num, films) {
    this.name = name;
    this.id = id;
    this.films = films;
    this.num = num;
    this.activeFilms = [];
    this.tableDiv = null;
}
Theater.prototype.constructor = Theater;

Theater.prototype.addFilm = function(newFilm) {
    this.films.push(newFilm);
};

Theater.prototype.toggleFilm = function(film) {
    if (this.activeFilms.contains(film)) {
        this.activeFilms.remove(film);
    } else this.activeFilms.push(film);
};

$.tablesorter.addParser({
    // set a unique id
    id: 'showtime',
    is: function(s) {
        // return false so this parser is not auto detected
        return false;
    },
    format: function(s, table, cell) {
        // format your data for normalization
        var sortNum = $(cell).data('sortNum');
        return sortNum;
    },
    // set type, either numeric or text
    type: 'numeric'
});


// Set things up
var theaters = [];
var theaterMap = {};
var tableMap = {};
// Gather list of theaters
var $allTheaters = $("div.theater");
var $settingsDiv = $('<div id="mdSettingsDiv"><b>Min. wait: </b><input type="text" class="mdSettings" id="mdFrontTolerance" name="FT" value="'+ FRONT_TOLERANCE +'" /> ' +
    '<b>Max. wait: </b><input type="text" class="mdSettings" id="mdEndTolerance" name="ET" value="'+ END_TOLERANCE +'" />' +
    '<b>Num. dips: </b><select class="mdSettings" id="mdNumDips" name="ND"><option value="2" selected>2</option><option value="3">3</option><option value="4">4</option>' +
    '<option value="5">5</option><option value="6">6</option><option value="7">7</option><option value="8">8</option></select>');
$("#results table:first").remove(); // just took up space at the top
$("#movie_results").before($settingsDiv);
$("input.mdSettings").blur(selectionsChanged);
$("#mdNumDips").change(selectionsChanged);

$allTheaters.each(function(idx) {
    var $theaterDiv = $(this);
    var theaterName = $theaterDiv.find("h2.name a").text();
    var theaterId = $theaterDiv.children("div.desc:first").attr("id");

// For each theater, gather list of movies
    var $movieDivs = $theaterDiv.find("div.movie");

    var newFilms = [];
    $movieDivs.each(function(idx2) {
        var $movieDiv = $(this);
        var movieId = theaterId + "_" + idx2;

// For each movie, gather names, lengths and showtimes
        var $movieNameA = $movieDiv.find("div.name a");
        var movieName = $movieNameA.text();
        AND_BUTTS && $movieNameA.text(movieName + " and Butts");

        var $newCheckbox = $('<input type="checkbox" />');
        $newCheckbox.data("theater", theaterId);
        $newCheckbox.data("movie", idx2);
        $newCheckbox.attr("id", theaterId + "-" + idx2);
        $newCheckbox.change(movieCheckboxChange);

        $movieNameA.before($newCheckbox);

// Cute stuff
        $movieDiv.click(function(event) {
            event.target.tagName !== "INPUT" && $newCheckbox.click();
            return true;
        });

// Find movie length
        var movieLength = 90;
        var lengthMatches = $movieDiv.find("span.info").text().match(/\D*(?:(\d+)h)?\D*(\d+)m/);
        if (lengthMatches) {
            var hours = parseInt(lengthMatches[1]);
            var mins = parseInt(lengthMatches[2]);
            mins = mins >= 0 ? mins : 90;
            mins += hours > 0 ? hours * 60 : 0;
            movieLength = mins;
        }


// Deal with showtimes
        var newTimes = [];
        var $movieTimes = $movieDiv.find("div.times > span");

        var am1 = -1, am2 = -1, pm1 = -1;
        $movieTimes.each(function(idx) {
            var timeText = $(this).text();
            var matches = timeText.match(/(\d+):(\d+)\s*(am|pm)?/);
            if (matches[3] === 'am') {
                if (am1 == -1) {
                    am1 = idx;
                } else {
                    am2 = idx;
                }
            } else if (matches[3] === 'pm') {
                pm1 = idx;
            }
        });

        $movieTimes.each(function(idx) {
            var matches = $(this).text().match(/((\d+):(\d+))\s*(am|pm)?/);
            var timeStr = matches[1];
            var momTime = moment();
			if (pm1 == -1) {
				momTime = moment(timeStr + 'am', 'hh:mma');
            } else if (am1 < pm1) {
                if (idx <= am1) {
                    momTime = moment(timeStr + 'am', 'hh:mma');
                } else if (idx > pm1) {
                    momTime = moment(timeStr + 'am', 'hh:mma');
                    momTime.add(1, 'days');
                } else {
                    momTime = moment(timeStr + 'pm', 'hh:mma');
                }
            } else {
                if (idx <= pm1) {
                    momTime = moment(timeStr + 'pm', 'hh:mma');
                } else {
                    momTime = moment(timeStr + 'am', 'hh:mma');
                    momTime.add(1, 'days');
                }
            }
            newTimes.push(momTime);
        });

        var newFilm = new Film(movieName, movieId, movieLength, newTimes);
        newFilm.checkBox = $newCheckbox;
        newFilm.div = $movieDiv;
        newFilms[idx2] = newFilm;

    });

    var newTheater = new Theater(theaterName, theaterId, idx, newFilms);

    theaters.push(newTheater);
    theaterMap[theaterId] = newTheater;
});

function movieDivEnter(event) {
    var id = event.data.id;
    $('table.mdTable td.title_cell, table.mdTable td.time_cell')
        .filter(function () {
            return $(this).data("movieId") === id;
        })
        .addClass('times_table_highlight');
}

function movieDivLeave() {
        $('table.mdTable td.times_table_highlight').removeClass('times_table_highlight');
}

// Runs whenever a checkbox is checked or unchecked
function movieCheckboxChange(event) {
    var $checkBox = $(event.target);
    var theaterId = $checkBox.data("theater");
    var filmNum = $checkBox.data("movie");
    var theater = theaterMap[theaterId];
    var film = theater.films[filmNum];
    var $movieDiv = film.div;

    film.selected = $checkBox.prop("checked");
    if (film.selected) {
        theater.activeFilms.push(film);
        $movieDiv.addClass("mdSelected");
        $movieDiv.mouseenter({'id': film.id}, movieDivEnter);
        $movieDiv.mouseleave(movieDivLeave);
    } else {
        theater.activeFilms = theater.activeFilms.filter(function(item) {
            return item.id !== film.id;
        });
        $movieDiv.removeClass("mdSelected");
        $movieDiv.off("mouseenter");
        $movieDiv.off("mouseleave");
    }

    selectionsChanged(event, theater);
    $movieDiv.mouseenter();
    return false;
}

// Runs whenever there is a change in selected movies (with theater ID) or in the settings (w/o ID)
function selectionsChanged(event, theater) {
    if (theater) {
        checkTheater(theater)
    } else {
        $.each(theaters, function () {
            checkTheater(this);
        });
    }
    return false;
}

// Check theater for selections. If there are enough selections, makeTable.
function checkTheater(theater) {
    if (theater.activeFilms.length >= NUM_DIPS) {
        var $newTable = makeTable(theater.activeFilms, theater.id);

        if ($newTable) {
            $newTable.hide();
            $newTable.children("table").tablesorter({
                theme: 'blue',
                headers: {
                    1: {
                        sorter:'showtime'
                    },
                    3: {
                        sorter:'showtime'
                    },
                    5: {
                        sorter:'showtime'
                    },
                    7: {
                        sorter:'showtime'
                    },
                    9: {
                        sorter:'showtime'
                    },
                    11: {
                        sorter:'showtime'
                    }
                }
            });
            $('#' + theater.id).next().after($newTable);

            if (theater.tableDiv !== null) {
                theater.tableDiv.remove();
                theater.tableDiv = $newTable;
                theater.tableDiv.show();
            } else {
                theater.tableDiv = $newTable;
                theater.tableDiv.show(FADE_SPEED);

            }
        } else if (theater.tableDiv !== null) {
            theater.tableDiv.hide(FADE_SPEED, function() { theater.tableDiv.remove(); theater.tableDiv = null; });
        }
    } else {
        if (theater.tableDiv !== null) {
            theater.tableDiv.hide(FADE_SPEED, function() { theater.tableDiv.remove(); theater.tableDiv = null; });
        }
    }
}

// Figures out which selected films can be multi-dipped
function makeTable(films, tId) {
    log("making table");
    var selected_films = films;
    var matches = [];
    var front_tolerance = parseInt($("#mdFrontTolerance").prop("value"));
    var end_tolerance = parseInt($("#mdEndTolerance").prop("value"));
    var num_dips = parseInt($("#mdNumDips").prop("value"));

    for (var i = 0, len = selected_films.length; i < len; i++) {
        var film1 = selected_films[i];
        var times1 = film1.times;
        var len1 = film1.len;
        for (var j = i + 1; j < len; j++) {
            var film2 = selected_films[j];
            if ( !(film1.title.indexOf(film2.title) == 0 && film1.title.lastIndexOf("3D") > 0) &&
                !(film2.title.indexOf(film1.title) == 0 && film2.title.lastIndexOf("3D") > 0)) {
                var times2 = film2.times;
                var len2 = film2.len;

                times1.forEach(function (val1) {
                    times2.forEach(function (val2) {
                        var match;
                        var diff2 = val1.diff(val2, 'minutes');
                        var diff1 = val2.diff(val1, 'minutes');

                        if (diff1 - len1 >= front_tolerance && diff1 - len1 <= end_tolerance) {
                            match = {
                                m: [film1, film2],
                                t: [val1, val2],
                                wait: [diff1 - len1]
                            };
                            matches.push(match);
                        } else if (diff2 - len2 >= front_tolerance && diff2 - len2 <= end_tolerance) {
                            match = {
                                m: [film2, film1],
                                t: [val2, val1],
                                wait: [diff2 - len2]
                            };
                            matches.push(match);
                        }
                    });
                });
            }
        }
    }

    log('num dips: ' + num_dips);
    var new_matches;
    var dipnum = 2;
    while (dipnum < num_dips && matches.length > 0) {
        new_matches = [];
        $.each(matches, function() {
            var this_match = this;
            $.each(selected_films, function() {
                var film2 = this;
                var nogood = false;
                $.each(this_match.m, function() {
                    if (    this.title === film2.title ||
                            (this.title.indexOf(film2.title) == 0 && this.title.lastIndexOf("3D") > 0) ||
                            (film2.title.indexOf(this.title) == 0 && film2.title.lastIndexOf("3D") > 0)
                    ) {
                        nogood = true;
                        return false; // break
                    }
                });
                if (nogood) return true; // continue

                var film1 = this_match.m[dipnum - 1];
                var val1 = this_match.t[dipnum - 1];
                var len1 = film1.len;
                var times2 = film2.times;

                times2.forEach(function (val2) {
                    var diff1 = val2.diff(val1, 'minutes');

                    if (diff1 - len1 >= front_tolerance && diff1 - len1 <= end_tolerance) {
                        var new_match = {};
                        new_match.m = this_match.m.concat(film2);
                        new_match.t = this_match.t.concat(val2);
                        new_match.wait = this_match.wait.concat(diff1 - len1);
                        new_matches.push(new_match);
                    }
                });
            });
        });

        dipnum++;
        console.log("new matches: " + new_matches.length);
        matches = new_matches;
    }


    if (matches.length > 0) {
        console.log(matches.length + " matches");
        var html = "";

        // Sort the matched pairs of movies by the first movie's starting time.
        matches.sort(function(a, b) {
            var diff = a.t[0].diff(b.t[0], 'minutes');
            return diff;
        });

        // Create the html table
        html = '<div class="mdTableDiv" id="' + tId + 'mdTableDiv"> <table id="' + tId + 'mdTable" class="tablesorter mdTable"> <thead> <tr> ';
        for (var mnum = 1; mnum <= num_dips; mnum++) {
            html += '<th>Movie ' + mnum + '</th> <th>Time ' + mnum + '</th>';
            if (mnum > 1) {
                html += '<th>Wait</th>';
            }
        }
        html += ' </tr> </thead> <tbody>';
        var fmt = 'hh:mm A';

        matches.forEach(function(val) {
            var s = '<tr> ';
            for (mnum = 0; mnum < num_dips; mnum++) {
                s += '<td class="title_cell" data-movie-id="' + val.m[mnum].id + '">' + val.m[mnum].title +
                    '</td> <td class="time_cell" data-movie-id="' + val.m[mnum].id + '" data-sort-num="' +
                    val.t[mnum].valueOf() + '"><b>' + val.t[mnum].format(fmt) + '</b></td> ';
                if (mnum > 0) {
                    s+= '<td class="wait_cell"><b>' + val.wait[mnum - 1] + '</b> min wait</td> ';
                }
            }
            s += '</tr>\n';

            html += s;
        });

        html += "</tbody> </table> </div>";

        return $(html);

    } else return null;
}


// Add custom CSS
var userStyles0 =
    "div.mdDiv { display: inline-block; !important}" +
    "div.movie:hover { background-color: #FFF8D4;}" +
    "a.mdCheck { display: inline-block; border: 2px solid #588220; border-radius: 8px; background-color: #588220; color: #D8FFA8; " +
    "padding: 2px 5px; margin-right: 5px; margin-bottom: 5px; line-height: 24px; !important}" +
    "a.mdCheck:link { text-decoration: none; }" +
    "div.mdSelected { background-color: #CCFFBB; !important}" +
    "div.mdSelected:hover { background-color: #99FF88 }" +
    ".theater .showtimes { margin-bottom: 0px; !important}" +
    "div.theater { margin-bottom: 40px; }" +
    ".mdTable td.wait_cell { color: #888888; !important }" +
    ".mdTable td.times_table_highlight { background-color: #CCFFBB; !important }" +
    "#mdSettingsDiv b { font-size: 1.1em; }" +
    "#mdSettingsDiv input { margin-right: 5px; font-size: 1em; }" +
    "#mdSettingsDiv select { margin-right: 5px; font-size: 1em; width: 4em; }" +
    "";

var userStyles3 = "" +
    ".tablesorter-blue {" +
        "	width: auto;" +
        "	background-color: #fff;" +
        "	margin: 10px 0 15px;" +
        "	text-align: left;" +
        "	border-spacing: 0;" +
        "	border: #cdcdcd 1px solid;" +
        "	border-width: 1px 0 0 1px;" +
        "}" +
        ".tablesorter-blue th," +
        ".tablesorter-blue td {" +
        "	border: #cdcdcd 1px solid;" +
        "	border-width: 0 1px 1px 0;" +
        "}" +
        "" +
        "/* header */" +
        ".tablesorter-blue th," +
        ".tablesorter-blue thead td {" +
        "	font: bold 12px/18px Arial, Sans-serif;" +
        "	color: #000;" +
        "	background-color: #99bfe6;" +
        "	border-collapse: collapse;" +
        "	padding: 6px;" +
        "	text-shadow: 0 1px 0 rgba(204, 204, 204, 0.7);" +
        "}" +
        ".tablesorter-blue tbody td," +
        ".tablesorter-blue tfoot th," +
        ".tablesorter-blue tfoot td {" +
        "	padding: 6px;" +
        "	vertical-align: top;" +
        "}" +
        ".tablesorter-blue .header," +
        ".tablesorter-blue .tablesorter-header {" +
        "	/* black (unsorted) double arrow */" +
        "	background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==);" +
        "	/* white (unsorted) double arrow */" +
        "	/* background-image: url(data:image/gif;base64,R0lGODlhFQAJAIAAAP///////yH5BAEAAAEALAAAAAAVAAkAAAIXjI+AywnaYnhUMoqt3gZXPmVg94yJVQAAOw==); */" +
        "	/* image */" +
        "	/* background-image: url(images/black-unsorted.gif); */" +
        "	background-repeat: no-repeat;" +
        "	background-position: center right;" +
        "	padding: 6px 18px 6px 6px;" +
        "	white-space: normal;" +
        "	cursor: pointer;" +
        "}" +
        ".tablesorter-blue .headerSortUp," +
        ".tablesorter-blue .tablesorter-headerSortUp," +
        ".tablesorter-blue .tablesorter-headerAsc {" +
        "	background-color: #9fbfdf;" +
        "	/* black asc arrow */" +
        "	background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7);" +
        "	/* white asc arrow */" +
        "	/* background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjI8Bya2wnINUMopZAQA7); */" +
        "	/* image */" +
        "	/* background-image: url(images/black-asc.gif); */" +
        "}" +
        ".tablesorter-blue .headerSortDown," +
        ".tablesorter-blue .tablesorter-headerSortDown," +
        ".tablesorter-blue .tablesorter-headerDesc {" +
        "	background-color: #8cb3d9;" +
        "	/* black desc arrow */" +
        "	background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAACMtMP///yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7);" +
        "	/* white desc arrow */" +
        "	/* background-image: url(data:image/gif;base64,R0lGODlhFQAEAIAAAP///////yH5BAEAAAEALAAAAAAVAAQAAAINjB+gC+jP2ptn0WskLQA7); */" +
        "	/* image */" +
        "	/* background-image: url(images/black-desc.gif); */" +
        "}" +
        ".tablesorter-blue thead .sorter-false {" +
        "	background-image: none;" +
        "	cursor: default;" +
        "	padding: 6px;" +
        "}" +
        "" +
        "/* tfoot */" +
        ".tablesorter-blue tfoot .tablesorter-headerSortUp," +
        ".tablesorter-blue tfoot .tablesorter-headerSortDown," +
        ".tablesorter-blue tfoot .tablesorter-headerAsc," +
        ".tablesorter-blue tfoot .tablesorter-headerDesc {" +
        "	/* remove sort arrows from footer */" +
        "	background-image: none;" +
        "}" +
        "" +
        "/* tbody */" +
        ".tablesorter-blue td {" +
        "	color: #3d3d3d;" +
        "	background-color: #fff;" +
        "	padding: 6px;" +
        "	vertical-align: top;" +
        "}" +
        "" +
        "/* hovered row colors" +
        " you'll need to add additional lines for" +
        " rows with more than 2 child rows" +
        " */" +
        ".tablesorter-blue tbody > tr:hover > td," +
        ".tablesorter-blue tbody > tr:hover + tr.tablesorter-childRow > td," +
        ".tablesorter-blue tbody > tr:hover + tr.tablesorter-childRow + tr.tablesorter-childRow > td," +
        ".tablesorter-blue tbody > tr.even:hover > td," +
        ".tablesorter-blue tbody > tr.even:hover + tr.tablesorter-childRow > td," +
        ".tablesorter-blue tbody > tr.even:hover + tr.tablesorter-childRow + tr.tablesorter-childRow > td {" +
        "	background: #d9d9d9;" +
        "}" +
        ".tablesorter-blue tbody > tr.odd:hover > td," +
        ".tablesorter-blue tbody > tr.odd:hover + tr.tablesorter-childRow > td," +
        ".tablesorter-blue tbody > tr.odd:hover + tr.tablesorter-childRow + tr.tablesorter-childRow > td {" +
        "	background: #bfbfbf;" +
        "}" +
        "" +
        "/* table processing indicator */" +
        ".tablesorter-blue .tablesorter-processing {" +
        "	background-position: center center !important;" +
        "	background-repeat: no-repeat !important;" +
        "	/* background-image: url(../addons/pager/icons/loading.gif) !important; */" +
        "	background-image: url('data:image/gif;base64,R0lGODlhFAAUAKEAAO7u7lpaWgAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBCgACACwAAAAAFAAUAAACQZRvoIDtu1wLQUAlqKTVxqwhXIiBnDg6Y4eyx4lKW5XK7wrLeK3vbq8J2W4T4e1nMhpWrZCTt3xKZ8kgsggdJmUFACH5BAEKAAIALAcAAAALAAcAAAIUVB6ii7jajgCAuUmtovxtXnmdUAAAIfkEAQoAAgAsDQACAAcACwAAAhRUIpmHy/3gUVQAQO9NetuugCFWAAAh+QQBCgACACwNAAcABwALAAACE5QVcZjKbVo6ck2AF95m5/6BSwEAIfkEAQoAAgAsBwANAAsABwAAAhOUH3kr6QaAcSrGWe1VQl+mMUIBACH5BAEKAAIALAIADQALAAcAAAIUlICmh7ncTAgqijkruDiv7n2YUAAAIfkEAQoAAgAsAAAHAAcACwAAAhQUIGmHyedehIoqFXLKfPOAaZdWAAAh+QQFCgACACwAAAIABwALAAACFJQFcJiXb15zLYRl7cla8OtlGGgUADs=') !important;" +
        "}" +
        "" +
        "/* Zebra Widget - row alternating colors */" +
        ".tablesorter-blue tbody tr.odd td {" +
        "	background-color: #ebf2fa;" +
        "}" +
        ".tablesorter-blue tbody tr.even td {" +
        "	background-color: #fff;" +
        "}" +
        "" +
        "/* Column Widget - column sort colors */" +
        ".tablesorter-blue td.primary," +
        ".tablesorter-blue tr.odd td.primary {" +
        "	background-color: #99b3e6;" +
        "}" +
        ".tablesorter-blue tr.even td.primary {" +
        "	background-color: #c2d1f0;" +
        "}" +
        ".tablesorter-blue td.secondary," +
        ".tablesorter-blue tr.odd td.secondary {" +
        "	background-color: #c2d1f0;" +
        "}" +
        ".tablesorter-blue tr.even td.secondary {" +
        "	background-color: #d6e0f5;" +
        "}" +
        ".tablesorter-blue td.tertiary," +
        ".tablesorter-blue tr.odd td.tertiary {" +
        "	background-color: #d6e0f5;" +
        "}" +
        ".tablesorter-blue tr.even td.tertiary {" +
        "	background-color: #ebf0fa;" +
        "}" +
        "";

GM_addStyle(userStyles3);
GM_addStyle(userStyles0);


/// REMOVE GOOGLE LINK TRACKING ///
// http://google.com/url?q=http://www.imdb.com/title/tt2294449/&sa=X&oi=moviesi&ii=0&usg=AFQjCNGscoN77bL-4GPf_VHQGumRZwQtAg
// http://google.com/url?q=http://www.youtube.com/watch%3Fv%3DVQqUOvQKPBg&sa=X&oi=movies&ii=0&usg=AFQjCNE3UMhKGsCv8WI8pgS-LMhnI2e0fA

$('a[href^="/url?q="]').each(function() {
    this.href = this.href.replace(/^https?:\/\/google\.com\/url\?q=/, '');
    this.href = this.href.replace(/&(?:sa|oi|ii|usg)=.*/, '');
    this.href = decodeURIComponent(this.href);
    this.href = this.href.replace(/afid=goog&?/, '');
    this.href = this.href.replace(/source=google&?/, '');
});