Greasy Fork

Batoto MyFollows

Allows you to filter your follows from comis search, links to MU and MAL from comic page, hides blood header, and other things.

目前为 2014-08-26 提交的版本。查看 最新版本

// ==UserScript== //
// @name         Batoto MyFollows
// @description  Allows you to filter your follows from comis search, links to MU and MAL from comic page, hides blood header, and other things.
// @namespace    https://greasyfork.org/users/168
// @include      http://www.batoto.net/*
// @include      https://www.batoto.net/*
// @exclude      http://www.batoto.net/ads/*
// @exclude      https://www.batoto.net/ads/*
// @include      http://www.mangaupdates.com/series.html?*
// @include      https://www.mangaupdates.com/series.html?*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_getResourceURL
// @run-at       document-start
// @version      14.08.25.01

// @resource     followingIconR   https://www.batoto.net/forums/public/style_images/Sylo/star.png
// @resource     muIconR          https://www.mangaupdates.com/favicon.ico
// @resource     malIconR         http://cdn.myanimelist.net/images/faviconv5.ico
// @resource     bIconR           https://www.batoto.net/forums/favicon.ico
// @resource     infoIconR        https://www.batoto.net/forums/public/style_images/master/information.png
// ==/UserScript==

var settings = {
    addIconsInHomepage: true,
    addIconsInSearch: true,
    followingIcon: GM_getResourceURL('followingIconR'),

    mangaupdatesLinkInComic: true,
    muIcon: GM_getResourceURL('muIconR'),

    malLinkInComic: true,
    malIcon: GM_getResourceURL('malIconR'),

    batotoLinkInMU: true,
    bIcon: GM_getResourceURL('bIconR'),

    infoIcon: 'https://www.batoto.net/forums/public/style_images/master/information.png',
    infoButtonOldFollows: true,
    durationInfoSlide: 0.0, //0.2 default batoto

    showTotalFollows: true,
    textColor: '#20B2AA',

    redirectToOldFollows: false,

    comicsPopupComicPage: true,
    comicsPopupForum: true,
    popupDelay: 1000,

    hideBloodHeader: true,

    hyperlinkDescription: true
};



var BMF = {},
    utils = {},
    layouts = {};

BMF.init = function() {
    layouts.setStyles();
    if (window.location.host == 'www.batoto.net') {
        if (settings.redirectToOldFollows) {
            BMF.redirectToOldFollows();
        }
        window.addEventListener('DOMContentLoaded', BMF.mainBatoto, false);

    } else if (window.location.host == 'www.mangaupdates.com') {
        window.addEventListener('DOMContentLoaded', BMF.mainMangaUpdates, false);
    }
};

BMF.isBlood = function() {
    var checkBlood = function() {
        var themeImg = document.getElementsByTagName('link')[1].href,
            currentIsBlood = themeImg.indexOf('Blood_Images') != -1,
            storageIsBlood = utils.getStorage('usingBlood') == true;
        if (currentIsBlood != storageIsBlood) {
            utils.setStorage('usingBlood', currentIsBlood);
            window.location.reload();
        }
    };
    window.addEventListener('DOMContentLoaded', checkBlood, false);
    BMF.isBlood = function() {
        return BMF.isBlood.value;
    };
    BMF.isBlood.value = utils.getStorage('usingBlood') || false;
    return BMF.isBlood();
};

BMF.redirectToOldFollows = function() {
    if (window.location.href == 'http://www.batoto.net/myfollows') {
        window.location.replace('/follows_comics');
    }
    var replaceLinks = function() {
        document.getElementById('nav_menu_4_trigger').href = '/follows_comics';
        if (window.location.pathname == '/') {
            document.getElementById('hook_watched_items').children[2].firstChild.href = '/follows_comics';
        }
    };
    window.addEventListener('DOMContentLoaded', replaceLinks, false);
};

BMF.mainBatoto = function() {
    var path = window.location.pathname;

    if (path == '/') {
        if (settings.addIconsInHomepage) BMF.addFollowIconsHome();

    } else if (path == '/myfollows') {
        BMF.saveFollowsList('new');

    } else if (path == '/follows_comics') {
        BMF.saveFollowsList('old');
        if (settings.infoButtonOldFollows) BMF.addInfoOldFollows();

        var newFollowsLinkBox = layouts.newFollowsLinkBox();
        document.getElementById('index_stats').appendChild(newFollowsLinkBox);

        BMF.categorizeFollows();
        if (settings.showTotalFollows) BMF.addTotalFollows();

    } else if (path == '/search') {
        BMF.followsInSearch();

    } else if (path.indexOf('/comic/_/comics') == 0) {
        if (settings.mangaupdatesLinkInComic) BMF.addSearchInMUButton();
        if (settings.malLinkInComic) BMF.addSearchInMALButton();
        if (settings.comicsPopupComicPage) BMF.setComicPopup();
        if (settings.hyperlinkDescription) {
            var desc = document.getElementsByTagName('tbody')[0].children[6].children[1],
                regExp = /(https?:\/\/([-\w\.]+)+(:\d+)?(\/([-\w\/_\.]*(\?\S+)?)?)?)/ig;
            desc.innerHTML = desc.innerHTML.replace(regExp, "<a href='$1'>$1</a>");
        }
    } else if (path.indeOf('/forums') == 0) {
        if (settings.comicsPopupForum) BMF.setComicPopup();
    }
};

BMF.addFollowIconsHome = function() {
    var followsList = utils.getStorage('followsList'),
        icon = utils.newImg('Following', settings.followingIcon, 'following_icon'),
        current = document.getElementById('loading_row_1'),
        i = 1;
    while (current) {
        if (followsList.indexOf(current.previousElementSibling.textContent) != -1) {
            current.parentNode.insertBefore(icon.cloneNode(false), current.parentNode.childNodes[2]);
        }
        i = i + 1;
        current = document.getElementById('loading_row_' + i.toString());
    }
};

BMF.saveFollowsList = function(page) {
    var followsList = [],
        comics, i, len;
    if (page == 'new') {
        comics = document.getElementsByClassName("clearfix")[9].children;
        i = 8;
    } else if (page == 'old') {
        comics = document.getElementsByTagName('strong');
        i = 1;
    }
    len = comics.length - 3;
    for (undefined; i < len; i++) {
        followsList.push(comics[i].textContent);
    }
    utils.setStorage('followsList', followsList);
};

BMF.addSearchInMUButton = function() {
    var title = document.getElementsByClassName('ipsType_pagetitle')[0],
        titleText = title.textContent.replace(/\(Doujinshi\)|\(doujinshi\)/g, ''),
        button = utils.newAnchorImg('Search in MangaUpdates', settings.muIcon, 'bmf_button');
    title.insertBefore(button, title.firstChild);
    utils.googleRedirectAnchor(button, 'mangaupdates.com/series.html?id=', titleText);
};

BMF.addSearchInMALButton = function() {
    var title = document.getElementsByClassName('ipsType_pagetitle')[0],
        titleText = title.textContent.replace(/\(Doujinshi\)|\(doujinshi\)/g, '(Doujin)'),
        button = utils.newAnchorImg('Search in MyAnimeList', settings.malIcon, 'bmf_button');
    title.insertBefore(button, title.firstChild);
    utils.googleRedirectAnchor(button, 'myanimelist.net/manga', titleText);
};

BMF.setComicPopup = function() {
    //uses a ipb function (the same that previews the profiles)
    var links = [].slice.call(document.getElementsByTagName('a')),
        length = links.length,
        anchor, href;
    for (var i = 0; i < length; i++) {
        anchor = links[i];
        href = anchor.href;
        if (href.indexOf('http://www.batoto.net/comic/_/') == 0 && href.search(/\?|#/) == -1) {
            anchor.className += ' _hovertrigger';
            anchor.setAttribute('hovercard-ref', 'comicPopup');
            anchor.setAttribute('hovercard-id', utils.getComicId(href));
        }
    }
    ipb.hoverCardRegister.initialize('comicPopup', {
        'w': '680px',
        'delay': settings.popupDelay,
        'position': 'auto',
        'ajaxUrl': 'http://www.batoto.net/comic_pop?',
        'getId': true,
        'setIdParam': 'id'
    });
};



BMF.addInfoOldFollows = function() {
    var titles = [].slice.call(document.getElementsByTagName('strong'), 0),
        len = titles.length - 3,
        infoButton = document.createElement('a'),
        title;
    infoButton.href = 'javascript:void(0)';
    infoButton.className = 'info_button';
    for (var i = 1; i < len; i++) {
        title = titles[i].parentNode;
        title.parentNode.insertBefore(infoButton.cloneNode(false), title);
        title.previousSibling.addEventListener('click', BMF.showInfoOldFollows, false);
    }
};

BMF.showInfoOldFollows = function() {
    // modification of the function used by batoto
    // batoto.net/js/shortcuts_20131231.js
    //(prototype.js)
    var that = this,
        comicid = utils.getComicId(that.nextSibling.href),
        divid = 'cId_' + comicid;
    if ($(divid) && $(divid).visible()) {
        Effect.SlideUp(divid, {
            duration: settings.durationInfoSlide
        });
    } else if ($(divid) && $(divid).innerHTML != '') {
        Effect.SlideDown(divid, {
            duration: settings.durationInfoSlide
        });
    } else {
        new Ajax.Updater(divid, ipb.vars['home_url'] + '/comic_pop', {
            parameters: {
                id: comicid
            },
            method: 'get',
            onLoaded: function() {
                var tr = document.createElement('tr'),
                    td = document.createElement('td'),
                    div = document.createElement('div'),
                    table = utils.getNode(that, "pa", 3),
                    nextRow = utils.getNode(that, "pa", 2, "neE", 1);
                div.id = divid;
                div.style.display = 'none';
                tr.className = 'info_row';
                if (BMF.isBlood()) {
                    if (that.parentNode.parentNode.className == 'row1') {
                        div.className += ' grey_row';
                    } else {
                        div.className += ' white_row';
                    }
                }
                td.setAttribute('colspan', '2');
                td.className = 'info_col';
                td.appendChild(div);
                tr.className = nextRow.className.replace(/row0|row1|altrow/g, '');
                tr.appendChild(td);
                table.insertBefore(tr, nextRow);
                that.parentNode.previousElementSibling.setAttribute('rowspan', '3');
            },
            onSuccess: function() {
                setTimeout(function() {
                    new Effect.SlideDown(divid, {
                        duration: settings.durationInfoSlide
                    });
                }, 50);
            }
        });
    }
};


BMF.updateFollowsList = function() {
    //only if there are no request being made currently
    var loading = document.getElementById('ajax_loading');
    if (!loading || loading.style.display == 'none') {
        new Ajax.Request('/follows_comics', {
            method: 'get',
            onSuccess: function(response) {
                var followsArray = [],
                    regExp = /topic"><strong>(.*?)<\/strong><\/a>/g,
                    comic = regExp.exec(response.responseText);
                while (comic) {
                    followsArray.push(utils.decodeHtmlEntity(comic[1]));
                    comic = regExp.exec(response.responseText);
                }
                utils.setStorage('followsList', followsArray);
                //updates all the previous comic results
                BMF.matchFollowsSearch('all');
                if (settings.addIconsInSearch) BMF.addIconsToMatches();
                if (BMF.hideMatches) BMF.changeMatchesDisplay('hide');
            }
        });
    }
};

BMF.categorizeFollows = function() {
    var rows = document.getElementsByTagName('table')[0].rows,
        len = rows.length,
        read = 0,
        reading = 0,
        noReads = 0,
        className, row1, row2, link1, link2;
    for (var a = 0; a < len; a = a + 2) {
        row1 = rows[a];
        row2 = rows[a + 1];
        if (row1.children[2].textContent != 'Last Read: No Record') {
            link1 = row1.children[2].children[0].href;
            link2 = row2.firstElementChild.children[1].href;
            if (link1 == link2) {
                className = ' bmf_read';
                read++;
            } else {
                className = ' bmf_reading';
                reading++;
            }
        } else {
            className = ' bmf_noreads';
            noReads++;
        }
        row1.className = row2.className += className;
    }
    var viewOptionsBox = layouts.viewOptionsBox(read, reading, noReads);
    document.getElementById("index_stats").insertBefore(viewOptionsBox, null);
    var views = function() {
        var table = document.getElementsByClassName('ipb_table')[0];
        if (this.value == 'hide') {
            table.classList.add('h_' + this.name);
        } else {
            table.classList.remove('h_' + this.name);
        }
    };
    for (var e = 1; e < 4; e++) {
        document.getElementById('show' + e).addEventListener('click', views, false);
        document.getElementById('hide' + e).addEventListener('click', views, false);
    }
};

BMF.addTotalFollows = function() {
    var total = document.getElementsByTagName('strong').length - 3,
        text = document.createElement('strong');
    text.className = 'total_follows';
    text.textContent = 'You are following ' + total + ' comics!';
    document.getElementsByClassName('maintitle')[0].appendChild(text);
};

BMF.followsInSearch = function() {
    BMF.matchFollowsSearch();
    if (settings.addIconsInSearch) BMF.addIconsToMatches();

    //button to update your follows list
    var searchBar = document.getElementsByClassName('input_submit')[2].parentNode,
        updateButton = document.createElement('a');
    updateButton.href = '<a href="javascript:void(0)';
    updateButton.title = 'Update Follows';
    updateButton.textContent = 'Update Follows';
    updateButton.className = 'input_submit update_follows';
    searchBar.insertBefore(updateButton, searchBar.childNodes[2]);
    updateButton.addEventListener('click', BMF.updateFollowsList, false);

    // exclude option in advance search
    var optionsBar = document.getElementById('advanced_options').children[0],
        optionInput = layouts.radioExcludeFollowsSearch();
    optionsBar.insertBefore(optionInput, optionsBar.children[6]);
    document.getElementById('incl_follows').addEventListener('click', BMF.changeMatchesDisplay, false);
    document.getElementById('excl_follows').addEventListener('click', BMF.changeMatchesDisplay, false);

    //watch for when results are added or reseted
    var tablesContainer = document.getElementById('comic_search_results'),
        observer = new MutationObserver(function(mutations) {
            if (mutations.length == 2 || mutations.length == 4) {
                BMF.matchFollowsSearch();
                if (settings.addIconsInSearch) BMF.addIconsToMatches();
            }
        });
    observer.observe(tablesContainer, {
        childList: true
    });
};

BMF.matchFollowsSearch = function(tableN) {
    this.lastMatches = []; //in case it has to add the icons
    var followsList = utils.getStorage('followsList'),
        comicRows, len, comicName, i;
    if (tableN == 'all') { //if function is called by update follows button then match all again
        comicRows = document.getElementsByTagName('strong');
        i = 1;
        len = comicRows.length - 3;
    } else { //just match the new table
        tableN = document.getElementsByClassName('chapters_list').length - 1;
        comicRows = document.getElementsByClassName('chapters_list')[tableN].getElementsByTagName('strong');
        i = 0;
        len = comicRows.length;
    }
    for (undefined; i < len; i++) {
        comicName = comicRows[i].textContent.trim();
        //classes are added to control the visibility of the rows/icons
        if (followsList.indexOf(comicName) != -1) {
            comicRows[i].parentNode.parentNode.className = 'bmf_match';
            this.lastMatches.push(comicRows[i]);
        } else {
            //css makes ".bmf_mismatch .following_icon" to be hidden
            //in case when updating it shows you unfollowed a comic
            comicRows[i].parentNode.parentNode.className = 'bmf_mismatch';
        }
    }
};

BMF.addIconsToMatches = function() {
    //only to lastMatches stored by BMF.matchFollowsSearch();
    var icon = utils.newImg('Following', settings.followingIcon, 'following_icon'),
        len = this.lastMatches.length,
        comic;
    for (var i = 0; i < len; i++) {
        comic = this.lastMatches[i].firstChild;
        if (comic.childNodes.length == 2) { //only if icon wasn't added previously (updating button)
            comic.insertBefore(icon.cloneNode(false), comic.firstChild.nextSibling);
        }
    }
    this.lastMatches = [];
};

BMF.changeMatchesDisplay = function(action) {
    //in case the function was called by click event in adv search option
    action = action.type == 'click' ? this.value : action;
    // #comic_search_results is parent of all the tables
    // css makes ".bmf_hide .bmf_match" rows to be hidden
    if (action == 'hide') {
        BMF.hideMatches = true;
        document.getElementById('comic_search_results').className = 'bmf_hide';
        //hides the info boxes of matches
        var infoBoxes = document.getElementsByClassName('ipsBox'),
            len = infoBoxes.length,
            infoBox;
        for (var u = 0; u < len; u++) {
            infoBox = infoBoxes[u].parentNode.parentNode;
            if (infoBox.previousElementSibling.className == 'bmf_match') {
                infoBox.style.display = 'none';
            }
        }
    } else if (action == 'show') {
        BMF.hideMatches = false;
        document.getElementById('comic_search_results').className = '';
    }
};


BMF.mainMangaUpdates = function() {
    var href = window.location.href;

    if (href.indexOf('https://www.mangaupdates.com/series.html?id=') != -1) {
        if (settings.batotoLinkInMU) {
            BMF.MU_addSearchInBatotoButton();
        }
    }
};

BMF.MU_addSearchInBatotoButton = function() {
    var title = document.getElementsByClassName('releasestitle')[0];
    if (title.textContent.indexOf('(Novel)') == -1) {
        var button = utils.newAnchorImg('Search in Batoto', settings.bIcon, 'bmf_button bmf_mu_button');
        title.parentNode.insertBefore(button, title);
        utils.googleRedirectAnchor(button, 'batoto.net/comic', title.textContent);
    }
};




layouts.setStyles = function() {
    if (BMF.isBlood() && settings.hideBloodHeader) {
        GM_addStyle(

        '#branding { ' +
            'position: absolute !important; } ');
    }

    GM_addStyle(

    '.following_icon { ' +
        'vertical-align: top; ' +
        'margin-left: 1px; } ' +

    '.bmf_mismatch .following_icon { ' +
        'display: none; } ' +

    '.bmf_hide .bmf_match { ' +
        'display: none; } ' +

    '.update_follows { ' +
        'position: absolute; ' +
        'top: 10px; ' +
        'left: 110px; ' +
        'font-weight: normal; } ' +

    '.info_button { ' +
        'padding: 0px 8px; ' +
        'margin-right: 2px; ' +
        'background-repeat: no-repeat; ' +
        'background-image: url(' + GM_getResourceURL('infoIconR') + '); } ' +

    '.h_read .bmf_read, .h_reading .bmf_reading, .h_noreads .bmf_noreads { ' +
        'display: none; } ' +

    '.info_col { ' +
        'padding: 0px !important; ' +
        'border: none !important; } ' +

    '.grey_row .ipsBox { ' +
        'background: rgba(0, 0, 0, 0.8); } ' +

    '.white_row .ipsBox { ' +
        'background: white; }' +

    '.total_follows { ' +
        'font-size: 70%; ' +
        'padding-left: 8px; ' +
        'color: ' + settings.textColor + '; ' +
        'display: inline; ' +
        'vertical-align: middle; } ' +

    '.bmf_button { ' +
        'color: #595959; ' +
        'background: #e4e4e4; ' +
        'background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f6f6f6), color-stop(100%,#d7d7d7)); ' +
        'box-shadow: -1px 1px 0px 0px #dfdcdc inset, 1px -1px 0px 0px #bfbfbf inset; ' +
        'border-radius: 5px; ' +
        'display: inline-block; ' +
        'width: 28px; ' +
        'height: 28px; ' +
        'vertical-align: -2px; ' +
        'text-align: center; ' +
        'margin: 0px 4px; }' +

    '.bmf_button:hover { ' +
        'box-shadow: 0px 0px 0px 1px #a8a8a8 inset, 1px 1px 3px 0px #bababa; ' +
        'color: #424242; } ' +

    '.bmf_button:active { ' +
        'background: #e6e6e6; ' +
        'box-shadow: 0px 1px 3px 0px #808080 inset; } ' +

    '.bmf_button img { ' +
        'display: inline-block; ' +
        'vertical-align: 4px; } ' +

    '.bmf_mu_button { ' +
        'vertical-align: 0 !important; ' +
        'margin-right: 4px !important; }' +

    '.bmf_mu_button img { ' +
        'vertical-align: 0px !important; ' +
        'margin-top: 6px; } ');
};




layouts.radioExcludeFollowsSearch = function() {
    var optionRadio = document.createElement('tr');
    optionRadio.innerHTML = '<td style=\"text-align: right; font-weight: bold;\">Include MyFollows:</td>' +
        '<td style=\"text-align: left; vertical-align: top; padding: 8px 0;\">' +
        '<label><input id=\"incl_follows\" type=\"radio\" name=\"follows\" value=\"show\" checked=\"checked\"> Yes</label>' +
        '<label><input id=\"excl_follows\" type=\"radio\" name=\"follows\" value=\"hide\" style=\"margin-left: 4px\"> No</label></td>';
    return optionRadio;
};
layouts.viewOptionsBox = function(read, reading, noReads) {
    var box = document.createElement("div");
    box.className = 'general_box clearfix;';
    box.innerHTML = '<h3>View Settings</h3><div class="_sbcollapsable">' +
        '<a href="javascript:void(0)" onclick=\"$(\'view\').toggle();">Alter Settings</a>' +
        '<div id="view" style="display: none;"><table><tbody>' +
        '<tr><td>All read (' + read + ')</td><td style="text-align: left; vertical-align: top; padding: 6px 0;">' +
        '<label><input id="show1" type="radio" name="read" value="show" checked="checked"> Show</label>' +
        '<label><input id="hide1" type="radio" name="read" value="hide" style="margin-left: 6px"> Hide</label></td></tr>' +
        '<tr><td>Reading (' + reading + ')</td><td style="text-align: left; vertical-align: top; padding: 6px 0;">' +
        '<label><input id="show2" type="radio" name="reading" value="show" checked="checked"> Show</label>' +
        '<label><input id="hide2" type="radio" name="reading" value="hide" style="margin-left: 6px"> Hide</label></td></tr>' +
        '<tr><td>No reads (' + noReads + ')</td><td style="text-align: left; vertical-align: top; padding: 6px 0;">' +
        '<label><input id="show3" type="radio" name="noreads" value="show" checked="checked"> Show</label>' +
        '<label><input id="hide3" type="radio" name="noreads" value="hide" style="margin-left: 6px"> Hide</label></td></tr>' +
        '</tbody></table></div></div>';
    return box;
};
layouts.newFollowsLinkBox = function() {
    var box = document.createElement('div');
    box.className = 'general_box alt clearfix';
    box.innerHTML = '<h3><img src="http://www.batoto.net/forums/public/style_images/master/star.png" alt=""> Follows by Chapters (new follows)</h3>' +
        '<div class="recent_activity _sbcollapsable"><div class="tab_toggle_content" style="text-align:center;">' +
        '<a href="/myfollows?noRedirect">Right here!</a></div></div>';
    return box;
};




utils.setStorage = function(key, value) {
    localStorage.setItem('BMF_' + key, JSON.stringify(value));
};
utils.getStorage = function(key) {
    return JSON.parse(localStorage.getItem('BMF_' + key));
};
utils.decodeHtmlEntity = function(encoded) {
    var div = document.createElement('div');
    div.innerHTML = encoded;
    return div.firstChild.nodeValue;
};
utils.newAnchorImg = function(title, src, className, href) {
    var button = document.createElement('a'),
        img = utils.newImg(title, src);
    button.className = className;
    button.href = href || 'javascript:void(0)';
    button.appendChild(img);
    return button;
};
utils.newImg = function(title, src, className) {
    var icon = document.createElement('img');
    icon.alt = '';
    icon.title = title;
    icon.src = src;
    icon.className = className || '';
    return icon;
};
utils.getComicId = function(url) {
    var temp = url.substring(url.lastIndexOf('r') + 1);
    return temp;

};
utils.getNode = function() {
    var args = arguments,
        node = args[0],
        len = args.length,
        a, action, times, b;
    for (a = 1; a < len; a = a + 2) {
        action = args[a];
        times = args[a + 1];
        b = 0;
        if (action == 'pa') { //undefined because of jslint
            for (undefined; b < times; b++) {
                node = node.parentNode;
            }
        } else if (action == 'fiE') {
            for (undefined; b < times; b++) {
                node = node.firstElementChild;
            }
        } else if (action == 'prE') {
            for (undefined; b < times; b++) {
                node = node.previousElementSibling;
            }
        } else if (action == 'neE') {
            for (undefined; b < times; b++) {
                node = node.nextElementSibling;
            }
        } else if (action == 'fi') {
            for (undefined; b < times; b++) {
                node = node.firstChild;
            }
        } else if (action == 'pr') {
            for (undefined; b < times; b++) {
                node = node.previousSibling;
            }
        } else if (action == 'ne') {
            for (undefined; b < times; b++) {
                node = node.nextSibling;
            }
        }
    }
    return node;
};
utils.googleRedirectAnchor = function(button, site, query) {
    //removes extra whitespaces, search operators and encode non latin characters
    query = encodeURIComponent(query.replace(/-|"|\*|\:/g, ' ').replace(/\s+/g, ' '));
    //first url sends you first to google and then redirects you,
    //this causes sometimes a half rendering of 'google.com' first.
    //second url can give you the direct link to the target site,
    //but only through a request with google as referer and response.finalUrl (GM_)
    var redirectURL = 'https://www.google.com/webhp?#btnI=I&sitesearch=' + site + '&q=' + query,
        requestURL = 'https://www.google.com/search?btnI=I&sitesearch=' + site + '&q=' + query;
    button.href = redirectURL;
    GM_xmlhttpRequest({
        method: 'HEAD',
        url: requestURL,
        headers: {
            Referer: 'https://www.google.com/'
        },
        onload: function(response) {
            if (response.finalUrl.indexOf(site) != -1) {
                button.href = response.finalUrl;
            } else {
                button.href = requestURL;
            }
        }
    });
};





BMF.init();