Raw Source
madhero / Batoto MyFollows

// ==UserScript== //
// @name         Batoto MyFollows
// @version      15.04.08.1
// @description  Filter your follows from comic search; info button and sorting in the old follows page; links between Batoto-MU-Mal and other features.
// @namespace    https://greasyfork.org/users/168
// @updateURL    https://greasyfork.org/scripts/365-batoto-myfollows/code/Batoto%20MyFollows.meta.js
// @downloadURL  https://greasyfork.org/scripts/365-batoto-myfollows/code/Batoto%20MyFollows.user.js
// @require      https://openuserjs.org/src/libs/sizzle/GM_config.js
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @include      *://bato.to/*
// @include      *://www.mangaupdates.com/*
// @include      *://myanimelist.net/*
// @run-at       document-start
// @noframes
// ==/UserScript==

//-----------------------------------------------------
// Batoto
//-----------------------------------------------------

var batoto = {
    init: function() {
        if (GM_config.get('batoto_defaultToOldFollows')) {
            this.defaultToOldFollows();
        }
        ready(function() {
            batoto.loadingImg.init();
            if (/^\/$/.test(path)) {
                batoto.p_home.main();
            } else if (/^\/myfollows/.test(path)) {
                batoto.p_newFollows.main();
            } else if (/^\/follows_comics/.test(path)) {
                batoto.p_oldFollows.main();
            } else if (/^\/search/.test(path)) {
                batoto.p_comicSearch.main();
            } else if (/^\/comic\/_\/comics\/$/.test(path)) {
                batoto.p_comicDir.main();
            } else if (/^\/comic\/_\/comics\/.+/.test(path)) {
                batoto.p_comic.main();
            } else if (/^\/group\/_\/.\/.+/.test(path)) {
                batoto.p_group.main();
            } else if (/^\/forums/.test(path)) {
                batoto.p_forums.main();
            }
            batoto.addOptionsButton();
        });
    },

    comicIdRegex: /bato\.to\/comic\/_\/.*\-r(\d+)\/?$/i,

    getComicId: function(url) {
        if (!url) return null;
        var id = url.match(batoto.comicIdRegex);
        return id !== null ? id[1] : id;
    },

    chapterRegex: /(?:Vol\.([0-9.]+) )?Ch\.(?:(?: *- *)?([0-9.]+)([a-uA-U])?){0,2}(?: ?\)?[vV]([0-9]))?/,

    parseChapter: function(string) {
        var match = string.match(batoto.chapterRegex),
            obj = {};
        if (!match) {
            return null;
        }
        obj.vol = match[1] === undefined ? false : match[1] * 1,
        obj.ch = match[2] === undefined ? true : match[2] * 1,
        obj.ch = match[3] === undefined ? obj.ch : obj.ch + (match[3].toLowerCase().charCodeAt(0) - 96) * 0.1,
        obj.ver = match[4] === undefined ? 1 : match[4] * 1;
        return obj;
    },

    parseDate: function(string) {
        var now, num, month, hour,
            match = string.match(/(\d+) (\w{3})\w* (\d+) - (\d+):(\d+) ?(\w+)?/);
        if (match) {
            month = match[2];
            if (month === 'Jan') month = 0;
            else if (month === 'Feb') month = 1;
            else if (month === 'Mar') month = 2;
            else if (month === 'Apr') month = 3;
            else if (month === 'May') month = 4;
            else if (month === 'Jun') month = 5;
            else if (month === 'Jul') month = 6;
            else if (month === 'Aug') month = 7;
            else if (month === 'Sep') month = 8;
            else if (month === 'Oct') month = 9;
            else if (month === 'Nov') month = 10;
            else if (month === 'Dec') month = 11;
            hour = match[4] === '12' ? 0 : match[4] * 1;
            hour = match[6] === 'AM' ? hour : hour + 12;
            return [match[3] * 1, month, match[1] * 1, hour, match[5] * 1];
        }
        match = string.match(/(An?|\d+) (week|day|hour|minute)s? ago/);
        if (match) {
            now = new Date();
            num = match[1] * 1 || 1;
            if (match[2] === 'week') now.setDate(now.getDate() - num * 7);
            else if (match[2] === 'day') now.setDate(now.getDate() - num);
            else if (match[2] === 'hour') now.setHours(now.getHours() - num);
            else if (match[2] === 'minute') now.setMinutes(now.getMinutes() - num);
            return [now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes()];
        }
        match = string.match(/(Today|Yesterday), (\d+):(\d+) (\w+)/);
        if (match) {
            now = new Date();
            if (match[1] === 'Yesterday') now.setDate(now.getDate() - 1);
            hour = match[2] === '12' ? 0 : match[2] * 1;
            hour = match[4] === 'AM' ? hour : hour + 12;
            return [now.getFullYear(), now.getMonth(), now.getDate(), hour, match[3] * 1];
        }
        return [0, 0, 0, 0, 0];
    },

    theme: null,

    getTheme: function() {
        if (this.theme === null) {
            var storedTheme = getStorage('batoto_theme', true);
            if (this.isLoggedIn()) {
                ready(function() {
                    var pageTheme = querySel('#new_skin_menucontent > .selected > a'),
                        dropList = document.getElementById('new_skin_menucontent');
                    if (pageTheme) {
                        pageTheme = pageTheme.textContent.toLowerCase().replace(/ .*/, '');
                    } else {
                        pageTheme = 'subway';
                    }
                    if (pageTheme !== storedTheme) {
                        setStorage('batoto_theme', pageTheme, true);
                        reload();
                        return;
                    }
                    dropList.addEventListener('click', function(e) {
                        pageTheme = e.target.textContent.toLowerCase().replace(/ .*/, '');
                        setStorage('batoto_theme', pageTheme, true);
                    }, true);
                });
            } else {
                setStorage('batoto_theme', 'subway', true);
            }
            this.theme = storedTheme || 'subway';
        }
        return this.theme;
    },

    loggedIn: null,

    isLoggedIn: function() {
        if (this.loggedIn === null) {
            var storedLoggedIn = getStorage('batoto_logged_in', true);
            ready(function() {
                var pageLoggedIn = Window.ipb.vars['member_id'] !== 0;
                setStorage('batoto_logged_in', pageLoggedIn, true);
                if (!storedLoggedIn && pageLoggedIn === true) {
                    reload();
                }
            });
            this.loggedIn = storedLoggedIn || false;
        }
        return this.loggedIn || false;
    },

    defaultToOldFollows: function() {
        var oldFollows = 'http://bato.to/follows_comics';
        if (path === '/myfollows' && location.search === '') {
            Window.stop();
            location.replace(oldFollows);
        }
        ready(function() {
            document.getElementById('nav_menu_4_trigger').href = oldFollows;
            if (path === '/') {
                querySel('#hook_watched_items > div:last-child > a').href = oldFollows;
            }
        });
    },

    setComicPopup: function(selector) {
        //uses the same ipb function that previews the profiles
        var links = selector ? querySelAll(selector) : document.getElementsByTagName('a'),
            popupConf, anchor, id;
        for (var i = 0, len = links.length; i < len; i++) {
            anchor = links[i];
            id = batoto.getComicId(anchor.getAttribute('href'));
            if (id !== null) {
                anchor.className += ' _hovertrigger';
                anchor.setAttribute('hovercard-ref', 'comicPopup');
                anchor.setAttribute('hovercard-id', id);
            }
        }
        popupConf = {
            'w': '680px',
            'delay': GM_config.get('batoto_comicsPopupDelay'),
            'position': 'auto',
            'ajaxUrl': Window.ipb.vars['home_url'] + '/comic_pop?',
            'getId': true,
            'setIdParam': 'id'
        };
        if (typeof cloneInto === 'function') {
            popupConf = cloneInto(popupConf, unsafeWindow);
        }
        Window.ipb.hoverCardRegister.initialize('comicPopup', popupConf);
    },

    followsList: {
        updating: false,

        update: function(callback) {
            if (!this.updating) {
                var self = this;
                self.updating = true;
                batoto.loadingImg.fadeIn();
                var request = new XMLHttpRequest();
                request.onreadystatechange = function() {
                    if (request.readyState === 4) {
                        if (request.status === 200) {
                            var followsList = [],
                                regex = /"View topic"><strong>(.*?)<\/strong>/g,
                                title = regex.exec(request.responseText);
                            while (title) {
                                followsList.push(decodeHtmlEntity(title[1]));
                                title = regex.exec(request.responseText);
                            }
                            setStorage('follows_list', toCharObject(followsList));
                            if (typeof callback === 'function') {
                                callback();
                            }
                        }
                        batoto.loadingImg.fadeOut();
                        self.updating = false;
                    }
                };
                request.open('GET', '/follows_comics', true);
                request.send();
            }
        }
    },

    loadingImg: {
        init: function() {
            var div = document.getElementById('ajax_loading');
            if (!div) {
                div = createElemHTML(Window.ipb.templates['ajax_loading']);
                div.style.display = 'none';
                document.getElementById('ipboard_body').appendChild(div);
            }
            if (typeof cloneInto === 'function') {
                this.config = cloneInto(this.config, Window);
            }
        },

        config: {
            duration: 0.25
        },

        queue: 0,

        effect: null,

        fadeIn: function() {
            this.queue++;
            if (this.queue === 1) {
                if (this.effect) {
                    this.effect.cancel();
                    this.effect = null;
                }
                this.effect = new Window.Effect.Appear('ajax_loading', this.config);
            }
        },

        fadeOut: function() {
            this.queue--;
            if (this.queue === 0) {
                if (this.effect) {
                    this.effect.cancel();
                    this.effect = null;
                }
                this.effect = new Window.Effect.Fade('ajax_loading', this.config);
            }
        }
    },

    addOptionsButton: function() {
        var btn = createElem('li'),
            anchor = createElem('a', {
                href: 'javascript:void(0)'
            });
        anchor.textContent = 'Open BFM Options';
        btn.appendChild(anchor);
        anchor.addEventListener('click', function() {
            GM_config.open();
        }, false);
        querySel('#footer_utilities .ipsList_inline.left').appendChild(btn);
    }
};


//------------------
// Specific pages
//------------------

batoto.p_home = {
    main: function() {
        if (GM_config.get('batoto_followingIcon_home') && batoto.isLoggedIn()) {
            this.addFollowsIcons();
        }
    },

    addFollowsIcons: function() {
        var followsList = getStorage('follows_list'),
            icon = createElem('img', {
                title: 'Following',
                alt: '',
                src: getResource('batoto_followingIcon'),
                class: 'bmf_following_icon'
            }),
            titles = querySelAll("td > a[style='font-weight:bold;']"),
            comic;
        if (!followsList) {
            batoto.followsList.update(this.addFollowsIcons);
            return;
        }
        for (var i = 1, len = titles.length; i < len; i += 2) {
            comic = titles[i];
            if (inCharObject(followsList, comic.textContent)) {
                comic.parentNode.insertBefore(icon.cloneNode(false), comic.previousSibling);
            }
        }
    }
};

batoto.p_newFollows = {
    main: function() {
        if (!batoto.isLoggedIn()) return;
        this.saveFollowsList();
    },

    saveFollowsList: function() {
        var followsList = [],
            titles = document.getElementById('categories').parentNode.children;
        for (var i = 7, len = titles.length - 7; i < len; i++) {
            followsList.push(titles[i].textContent);
        }
        setStorage('follows_list', toCharObject(followsList));
    }
};

batoto.p_oldFollows = {
    main: function() {
        if (!batoto.isLoggedIn()) return;
        this.getComics();
        this.saveFollowsList();
        this.sorting.main();

        if (GM_config.get('batoto_showTotalFollows')) {
            this.showTotalFollows();
        }
        if (GM_config.get('batoto_addInfoBtns')) {
            this.info.addBtns();
        }
        if (GM_config.get('batoto_addBoxToNewFollows')) {
            this.addBoxToNewFollows();
        }
        if (GM_config.get('batoto_categorizeFollows')) {
            this.categorizeFollows();
        }
    },

    comics: null,

    getComics: function() {
        if (this.gotComics) return;
        var nodes = document.getElementsByClassName('ipb_table')[0].getElementsByTagName('tr'),
            len = nodes.length,
            comics = this.comics = new Array(len / 2);
        for (var i = 0; i < len; i += 2) {
            comics[i / 2] = {
                first: nodes[i],
                second: nodes[i + 1]
            };
        }
        this.gotComics = true;
    },

    getComicsTitle: function() {
        if (this.gotComicsTitle) return;
        var comics = this.comics;
        for (var i = 0, len = comics.length; i < len; i++) {
            comics[i].title = comics[i].first.getElementsByTagName('strong')[0].textContent;
        }
        this.gotComicsTitle = true;
    },

    getComicsLastRead: function() {
        if (this.gotComicsLastRead) return;
        var comics = this.comics,
            comic, text, c;
        for (var i = 0, len = comics.length; i < len; i++) {
            comic = comics[i];
            text = comic.first.lastElementChild.textContent;
            comic.lastReadDate = batoto.parseDate(text);
            c = batoto.parseChapter(text);
            if (c === null) {
                comic.lastReadCh = false;
                comic.lastReadVol = false;
            } else {
                comic.lastReadCh = c.ch;
                comic.lastReadVol = c.vol;
            }
        }
        this.gotComicsLastRead = true;
    },

    getComicsLastUpdate: function() {
        if (this.gotComicsLastUpdate) return;
        var comics = this.comics,
            comic, dateText, chText, c;
        for (var i = 0, len = comics.length; i < len; i++) {
            comic = comics[i];
            dateText = comic.second.lastElementChild.textContent;
            chText = comic.second.firstElementChild.textContent;
            comic.lastUpdateDate = batoto.parseDate(dateText);
            c = batoto.parseChapter(chText);
            if (c === null) {
                comic.lastUpdateCh = false;
                comic.lastUpdateVol = false;
            } else {
                comic.lastUpdateCh = c.ch;
                comic.lastUpdateVol = c.vol;
            }
        }
        this.gotComicsLastUpdate = true;
    },

    getComicsUnreadCh: function() {
        if (this.gotUnreadCh) return;
        this.getComicsLastUpdate();
        this.getComicsLastRead();
        var comics = this.comics,
            comic;
        for (var i = 0, len = comics.length; i < len; i++) {
            comic = comics[i];
            if (typeof comic.lastUpdateCh === 'number' && comic.lastReadCh === false) {
                comic.unreadChs = comic.lastUpdateCh;
            } else if (typeof comic.lastUpdateCh === 'number' && typeof comic.lastReadCh === 'number') {
                if (comic.lastUpdateCh === comic.lastReadCh) {
                    comic.unreadChs = -0.03;
                } else {
                    comic.unreadChs = comic.lastUpdateCh - comic.lastReadCh;
                }
            } else if (comic.lastUpdateCh === false && comic.lastReadCh === false) {
                comic.unreadChs = -0.04;
            } else if (comic.lastUpdateCh === true && comic.lastReadCh === false) {
                comic.unreadChs = -0.01;
            } else {
                comic.unreadChs = -0.02;
            }
        }
        this.gotUnreadCh = true;
    },

    saveFollowsList: function() {
        var comics = this.comics,
            followsList = [];
        if (!this.gotComicsTitle) {
            this.getComicsTitle();
        }
        for (var i = 0, len = comics.length; i < len; i++) {
            followsList.push(comics[i].title);
        }
        setStorage('follows_list', toCharObject(followsList));
    },

    showTotalFollows: function() {
        var total = String(this.comics.length),
            elem = document.createElement('strong');
        elem.id = 'bmf_total_follows';
        elem.textContent = total + ' Comics!';
        document.getElementsByClassName('maintitle')[0].appendChild(elem);
    },

    sorting: {
        modes: {
            "Last Update's Date": {
                prepare: function() {
                    batoto.p_oldFollows.getComicsLastUpdate();
                },
                sortCallback: function(a, b) {
                    var arrayA = a.lastUpdateDate,
                        arrayB = b.lastUpdateDate,
                        i;
                    for (var i = 0; i < 5; i++) {
                        if (arrayB[i] > arrayA[i]) return 1;
                        if (arrayB[i] < arrayA[i]) return -1;
                    }
                    return 0;
                }
            },
            "Last Update's Ch": {
                prepare: function() {
                    batoto.p_oldFollows.getComicsLastUpdate();
                },
                sortCallback: function(a, b) {
                    var A = a.lastUpdateCh,
                        B = b.lastUpdateCh;
                    if (A === B) {
                        A = a.lastUpdateVol;
                        B = b.lastUpdateVol;
                        A = A === false ? -2 : (A === true ? -1 : A);
                        B = B === false ? -2 : (B === true ? -1 : B);
                        return B - A;
                    }
                    A = A === false ? -2 : (A === true ? -1 : A);
                    B = B === false ? -2 : (B === true ? -1 : B);
                    return B - A;
                }
            },
            "Last Read's Date": {
                prepare: function() {
                    batoto.p_oldFollows.getComicsLastRead();
                },
                sortCallback: function(a, b) {
                    var arrayA = a.lastReadDate,
                        arrayB = b.lastReadDate,
                        i;
                    for (var i = 0; i < 5; i++) {
                        if (arrayB[i] > arrayA[i]) return 1;
                        if (arrayB[i] < arrayA[i]) return -1;
                    }
                    return 0;
                }
            },
            "Last Read's Ch": {
                prepare: function() {
                    batoto.p_oldFollows.getComicsLastRead();
                },
                sortCallback: function(a, b) {
                    var A = a.lastReadCh,
                        B = b.lastReadCh;
                    if (A === B) {
                        A = a.lastReadVol;
                        B = b.lastReadVol;
                        A = A === false ? -2 : (A === true ? -1 : A);
                        B = B === false ? -2 : (B === true ? -1 : B);
                        return B - A;
                    }
                    A = A === false ? -2 : (A === true ? -1 : A);
                    B = B === false ? -2 : (B === true ? -1 : B);
                    return B - A;
                }
            },
            'Unread Chs': {
                prepare: function() {
                    batoto.p_oldFollows.getComicsUnreadCh();
                },
                sortCallback: function(a, b) {
                    return b.unreadChs - a.unreadChs;
                }
            },
            'Title': {
                prepare: function() {
                    batoto.p_oldFollows.getComicsTitle();
                },
                sortCallback: function(a, b) {
                    if (a.title > b.title) return 1;
                    if (a.title < b.title) return -1;
                    return 0;
                }
            }
        },

        main: function() {
            if (GM_config.get('batoto_autoSort')) {
                this.run(GM_config.get('batoto_defaultSort'));
            }
            var self = this,
                mainTitle = document.getElementsByClassName('maintitle')[0],
                handler = function() {
                    self.run(this.textContent);
                },
                btnClass = 'bmf_sort_btn',
                mode, btn;

            if (GM_config.get('batoto_adaptSortButton')) {
                btnClass += ' f_icon';
            }
            if (batoto.getTheme() === 'blood') {
                btnClass += ' ipsButton_secondary';
            } else {
                btnClass += ' ipsButton';
            }

            for (var modeName in self.modes) {
                mode = self.modes[modeName];
                btn = createElem('button', {
                    class: btnClass,
                    href: 'javascript:void(0)'
                });
                btn.textContent = modeName;
                btn.addEventListener('click', handler, false);
                mainTitle.appendChild(btn);
            }
        },

        prev: null,
        reversed: false,

        run: function(modeName) {
            var comics = batoto.p_oldFollows.comics,
                modes = this.modes,
                mode = modes[modeName],
                revI;
            if (typeof mode.prepare === 'function') {
                mode.prepare();
            }

            if (this.prev === modeName) {
                this.reversed = !this.reversed;
            } else {
                this.reversed = false;
                this.prev = modeName;
            }
            revI = this.reversed ? -1 : 1;
            comics.sort(function(a, b) {
                var i = mode.sortCallback(a, b) * revI;
                if (i === 0) {
                    i = modes.Title.sortCallback(a, b);
                }
                return i;
            });

            this.updateTable(true, true);
        },

        updateTable: function(updateSort, updateClasses) {
            if (!updateSort && !updateClasses) return;
            var table = querySel('.clearfix > table'),
                tbody = table.firstElementChild,
                comics = batoto.p_oldFollows.comics,
                rowEven = false,
                node1, nodeInfo, node2;
            table.removeChild(tbody);
            for (var i = 0, len = comics.length; i < len; i++) {
                node1 = comics[i].first;
                nodeInfo = node1.nextElementSibling;
                node2 = comics[i].second;
                if (updateSort) {
                    tbody.appendChild(node1);
                    if (nodeInfo !== node2) {
                        tbody.appendChild(nodeInfo);
                    }
                    tbody.appendChild(node2);
                }
                if (updateClasses) {
                    node1.classList.toggle('row1', rowEven);
                    node1.classList.toggle('row0', !rowEven);
                    if (nodeInfo !== node2) {
                        nodeInfo.classList.toggle('row1', rowEven);
                        nodeInfo.classList.toggle('row0', !rowEven);
                    }
                    node2.classList.toggle('row1', rowEven);
                    node2.classList.toggle('row0', !rowEven);
                    rowEven = !rowEven;
                }
            }
            table.appendChild(tbody);
        }
    },

    info: {
        addBtns: function() {
            var that = batoto.p_oldFollows,
                comics = that.comics,
                table = querySel('.clearfix > table > tbody'),
                infoBtn = createElem('a', {
                    href: 'javascript:void(0)',
                    class: 'bmf_info_button'
                }),
                title;
            for (var i = 0, len = comics.length; i < len; i++) {
                title = comics[i].first.getElementsByTagName('a')[1];
                title.parentNode.insertBefore(infoBtn.cloneNode(false), title);
            }
            table.addEventListener('click', function(e) {
                if (e.target.className === 'bmf_info_button') {
                    that.info.showBox(e.target);
                }
            }, false);
        },

        showBox: function(infoBtn) {
            // modification of the function used by batoto:
            // bato.to/js/shortcuts_20131231.js
            //(prototype.js)
            var anchor = infoBtn.nextElementSibling,
                comicId = batoto.getComicId(anchor.getAttribute('href')),
                div = document.getElementById('cId_' + comicId);
            if (div === null) {
                div = this.addBoxContainer(infoBtn, comicId);
            }
            if (div.children.length === 0) {
                batoto.loadingImg.fadeIn();

                var request = new XMLHttpRequest();
                request.onreadystatechange = function() {
                    if (request.readyState === 4) {
                        batoto.loadingImg.fadeOut();

                        if (request.status === 200) {
                            div.innerHTML = request.responseText;
                            setTimeout(function() {
                                div.style.display = '';
                            }, 30);
                        }
                    }
                };
                request.open('GET', '/comic_pop?id=' + comicId, true);
                request.send();

            } else if (div.style.display === '') {
                div.style.display = 'none';
            } else {
                div.style.display = '';
            }
        },

        addBoxContainer: function(infoBtn, comicId) {
            var nextRow = infoBtn.parentNode.parentNode.nextElementSibling,
                div = createElem('div', {
                    id: 'cId_' + comicId,
                    style: 'display: none;'
                }),
                tr = createElem('tr', {
                    class: nextRow.className.replace('altrow', '') + ' bmf_info_row'
                }),
                td = createElem('td', {
                    colspan: '2',
                    style: 'border-bottom-width:0 !important'
                });

            td.appendChild(div);
            tr.appendChild(td);
            querySel('.ipb_table > tbody').insertBefore(tr, nextRow);
            infoBtn.parentNode.previousElementSibling.setAttribute('rowspan', '3');
            return div;
        }
    },

    categorizeFollows: function() {
        var rows = document.getElementsByTagName('table')[0].rows,
            read = 0,
            reading = 0,
            noReads = 0,
            className, row1, row2, link1, link2, viewOptionsBox, views;
        for (var i = 0, len = rows.length; i < len; i = i + 2) {
            row1 = rows[i];
            row2 = rows[i + 1];
            if (row1.children[2].textContent !== 'Last Read: No Record') {
                link1 = row1.children[2].children[0].getAttribute('href');
                link2 = row2.firstElementChild.children[1].getAttribute('href');
                if (link1 === link2) {
                    className = ' bmf_read';
                    read++;
                } else {
                    className = ' bmf_reading';
                    reading++;
                }
            } else {
                className = ' bmf_noreads';
                noReads++;
            }
            row1.className = row2.className += className;
        }

        viewOptionsBox = createElemHTML(
            '<div class="general_box clearfix"><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>');
        document.getElementById('index_stats').insertBefore(viewOptionsBox, null);
        views = function() {
            var table = document.getElementsByClassName('ipb_table')[0];
            if (this.value === 'hide') {
                table.classList.add('bmf_hide_' + this.name);
            } else {
                table.classList.remove('bmf_hide_' + this.name);
            }
        };
        for (var i = 1; i < 4; i++) {
            document.getElementById('show' + i).addEventListener('click', views, false);
            document.getElementById('hide' + i).addEventListener('click', views, false);
        }
    },

    addBoxToNewFollows: function() {
        var box = document.createElement('div');
        box.className = 'general_box alt clearfix';
        box.innerHTML = '<h3><img src=" ' + Window.ipb.vars['rate_img_on'] + '" alt="">' +
            '\nFollows 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>';
        document.getElementById('index_stats').appendChild(box);
    }
};

batoto.p_comicSearch = {
    main: function() {
        if (!batoto.isLoggedIn()) return;

        this.runMatch('last');
        this.addUpdateFollowsBtn();
        this.addExcludeOption();
        this.watchForNewResults();
    },

    runMatch: function(table) {
        var comics = this.getMatchComics(table),
            addFollowingIcon = GM_config.get('batoto_followingIcon_search');
        if (comics === false) {
            batoto.followsList.update(function() {
                batoto.p_comicSearch.runMatch('last');
            });
            return;
        }
        var icon = createElem('img', {
            title: 'Following',
            alt: '',
            src: getResource('batoto_followingIcon'),
            class: 'bmf_following_icon'
        }),
            matches = comics[0],
            mismatches = comics[1],
            comic, title;

        for (var i = 0, len = matches.length; i < len; i++) {
            comic = matches[i];
            title = comic.firstChild;
            if (addFollowingIcon && title.childNodes.length === 2) {
                title.insertBefore(icon.cloneNode(false), title.firstChild.nextSibling);
            }
            comic.parentNode.parentNode.setAttribute('class', 'bmf_match');
        }

        for (var i = 0, len = mismatches.length; i < len; i++) {
            // if the icon was added but then you unfollowed the comic
            // and updated the list, then css will hide the icon
            mismatches[i].parentNode.parentNode.setAttribute('class', 'bmf_mismatch');
        }
    },

    getMatchComics: function(table) {
        var followsList = getStorage('follows_list'),
            matches = [],
            mismatches = [],
            comics, comic, len, i;

        if (!followsList) {
            return false;
        }

        if (table === 'all') {
            comics = document.getElementsByTagName('strong');
            i = 1;
            len = comics.length - 3;
        } else if (table === 'last') {
            table = document.getElementsByClassName('chapters_list').length - 1;
            comics = document.getElementsByClassName('chapters_list')[table].getElementsByTagName('strong');
            i = 0;
            len = comics.length;
        }

        for (; i < len; i++) {
            comic = comics[i];
            if (inCharObject(followsList, comic.textContent.trim())) {
                matches.push(comic);
            } else {
                mismatches.push(comic);
            }
        }
        return [matches, mismatches];
    },

    matchesVisibility: function(action) {
        if (action === 'hide') {
            document.getElementById('comic_search_results').className = 'bmf_hide_matches';
            // hides the info boxes of matches if shown
            var infoBoxes = document.getElementsByClassName('ipsBox'),
                infoBox;
            for (var i = 0, len = infoBoxes.length; i < len; i++) {
                infoBox = infoBoxes[i].parentNode.parentNode;
                if (infoBox.previousElementSibling.className === 'bmf_match') {
                    infoBox.style.display = 'none';
                }
            }
        } else if (action === 'show') {
            document.getElementById('comic_search_results').className = '';
        }
    },

    addExcludeOption: function() {
        var self = this,
            optionsBar = document.getElementById('advanced_options').children[0],
            optionInput = document.createElement('tr');
        optionInput.innerHTML =
            '<tr><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></tr>';
        optionsBar.insertBefore(optionInput, optionsBar.children[6]);
        document.getElementById('incl_follows').addEventListener('click', function() {
            self.matchesHidden = false;
            self.matchesVisibility('show');
        }, false);
        document.getElementById('excl_follows').addEventListener('click', function() {
            self.matchesHidden = true;
            self.matchesVisibility('hide');
        }, false);
    },

    watchForNewResults: function() {
        var self = this,
            tablesContainer = document.getElementById('comic_search_results'),
            observer = new MutationObserver(function(mutations) {
                if (mutations.length === 2 || mutations.length === 4) {
                    self.runMatch('last');
                }
            });
        observer.observe(tablesContainer, {
            childList: true
        });
    },

    addUpdateFollowsBtn: function() {
        var self = this,
            searchBar = querySel('#comic_search_form > div > div'),
            updateBtn = createElem('a', {
                id: 'bmf_upd_follows_btn',
                class: 'input_submit',
                title: 'Update Follows',
                href: 'javascript:void(0)'
            });
        updateBtn.textContent = 'Update Follows';
        searchBar.appendChild(updateBtn);
        updateBtn.addEventListener('click', function() {
            batoto.followsList.update(function() {
                self.runMatch('all');
                if (self.matchesHidden) {
                    self.matchesVisibility('hide');
                }
            });
        }, false);
    }
};

batoto.p_comicDir = {
    main: function() {
        if (GM_config.get('batoto_comicsPopup_comicDir')) {
            batoto.setComicPopup();
        }
    }
};

batoto.p_comic = {
    main: function() {
        if (GM_config.get('batoto_comicsPopup_comic')) {
            batoto.setComicPopup();
        }
        if (GM_config.get('i_batoto_mu')) {
            this.addInterlinkBtn('mu', 'Search in MangaUpdates');
        }
        if (GM_config.get('i_batoto_mal')) {
            this.addInterlinkBtn('mal', 'Search in MyAnimeList');
        }
        if (GM_config.get('batoto_hyperlinkDesc')) {
            this.doHyperlinkDesc();
        }
        if (GM_config.get('batoto_showMoreRecentBtn')) {
            this.addShowMoreRecentBtn();
        }
        if (GM_config.get('batoto_autoOpenImgSpoilers') || GM_config.get('batoto_addSpoilersType')) {
            this.doSpoilersTweaks();
        }
        if (GM_config.get('batoto_fixPageTitle')) {
            this.fixPageTitle();
        }
    },

    addInterlinkBtn: function(target, title) {
        var comicTitle = document.getElementsByClassName('ipsType_pagetitle')[0],
            comicName = comicTitle.textContent.replace(/\(doujinshi\)/gi, '(Doujin)'),
            iconUrl = getResource(target + '_icon'),
            button = createImgBtn(title, 'bmf_interlink_btn ipsButton_secondary', iconUrl);
        comicTitle.insertBefore(button, comicTitle.firstChild);
        interlinks.getUrl(target, comicName, location.href, function(url) {
            button.href = url;
        });
        button.addEventListener('click', function(event) {
            if (!event.ctrlKey || !event.shiftKey) return;
            event.preventDefault();
            interlinks.saveFromPrompt(location.href, target);
        }, false);
    },

    doHyperlinkDesc: function() {
        var desc = document.getElementsByTagName('tbody')[0].children[6].children[1],
            regex = /(\b(https?):\/\/[\-A-Z0-9+&@#\/%?=~_|!:,.;]*[\-A-Z0-9+&@#\/%=~_|])/gim;
        desc.innerHTML = desc.innerHTML.replace(regex, '<a href="$1">$1</a>');
    },

    addShowMoreRecentBtn: function() {
        var btn = createElem('div', {
            id: 'bmf_show_recent_btn'
        });
        var a = createElem('a', {
            class: 'input_submit',
            href: '/comic/_/comics/?sort_col=record_saved&sort_order=desc'
        });
        a.textContent = 'Show More';
        btn.appendChild(a);
        querySelAll('.general_box')[2].appendChild(btn);
    },

    doSpoilersTweaks: function() {
        var spoilers = document.getElementsByClassName('bbc_spoiler_wrapper'),
            autoOpen = GM_config.get('batoto_autoOpenImgSpoilers'),
            addType = GM_config.get('batoto_addSpoilersType'),
            spoiler, imgs, text, type;
        for (var i = 0, len = spoilers.length; i < len; i++) {
            spoiler = spoilers[i];
            imgs = spoiler.getElementsByClassName('bbc_img').length !== 0;
            text = spoiler.textContent.trim() !== '';
            if (imgs && !text && autoOpen) {
                spoiler.previousElementSibling.value = 'Hide';
                spoiler.firstElementChild.style.display = '';
            }
            if (addType) {
                if (!imgs && text) {
                    type = 'Spoiler (Text)';
                } else if (imgs && text) {
                    type = 'Spoiler (Text/Image)';
                } else if (imgs && !text) {
                    type = 'Spoiler (Image)';
                }
                spoiler.parentNode.firstElementChild.textContent = type;
            }
        }
    },

    fixPageTitle: function() {
        document.title = document.title.split('- Scanlations ')[0] + '- Batoto';
    }
};

batoto.p_group = {
    main: function() {
        if (GM_config.get('batoto_comicsPopup_group')) {
            batoto.setComicPopup();
        }
    }
};

batoto.p_forums = {
    main: function() {
        if (GM_config.get('batoto_comicsPopup_forums')) {
            batoto.setComicPopup();
        }
    }
};


//-----------------------------------------------------
// MangaUpdates
//-----------------------------------------------------

var mu = {
    init: function() {
        ready(function() {

            var url = location.href;
            if (/\/series\.html\?(?:.+&|&?)id=[^&]+/.test(url)) {
                mu.p_comic.main();
            }
            mu.addOptionsButton();

        });
    },

    comicIdRegex: /mangaupdates\.com\/series\.html\?(?:.+&|&?)id=([^&]+)/i,

    getComicId: function(url) {
        if (!url) return null;
        var id = url.match(mu.comicIdRegex);
        return id !== null ? id[1] : id;
    },

    addOptionsButton: function() {
        var trBtn = createElem('tr'),
            tr = createElem('tr'),
            td = createElem('td'),
            a = document.createElement('a');
        trBtn.className = 'newsname',
        trBtn.appendChild(a);
        td.height = '15',
        tr.appendChild(td);
        a.textContent = 'Open BMF Opts';
        a.style.color = 'rgb(49, 70, 110)';
        a.addEventListener('click', function() {
            GM_config.open();
        }, false);
        document.querySelector('#right_row2 tbody').appendChild(tr);
        document.querySelector('#right_row2 tbody').appendChild(trBtn);
    }
};

//------------------
// Specific pages
//------------------

mu.p_comic = {
    main: function() {
        if (GM_config.get('i_mu_mal')) {
            this.addInterlinkBtn('mal', ' Search in MyAnimeList');
        }
        if (GM_config.get('i_batoto_mu')) {
            this.addInterlinkBtn('batoto', ' Search in Batoto');
        }
    },

    addInterlinkBtn: function(target, title) {
        var comicTitle = document.getElementsByClassName('releasestitle')[0],
            comicName = comicTitle.textContent;
        if (comicName.indexOf('(Novel)') === -1) {
            var button = createImgBtn(title, 'bmf_interlink_btn', getResource(target + '_icon'));
            comicTitle.parentNode.insertBefore(button, comicTitle);
            interlinks.getUrl(target, comicName, location.href, function(url) {
                button.href = url;
            });
            button.addEventListener('click', function(event) {
                if (!event.ctrlKey || !event.shiftKey) return;
                event.preventDefault();
                interlinks.saveFromPrompt(location.href, target);
            }, false);
        }
    }
};


//-----------------------------------------------------
// MyAnimeList
//-----------------------------------------------------

var mal = {
    init: function() {
        ready(function() {

            if (/manga/.test(path)) {
                mal.p_comic.main();
            }
            mal.addOptionsButton();

        });
    },

    comicIdRegex: /myanimelist\.net\/manga\/(\d+)/i,

    getComicId: function(url) {
        if (!url) return null;
        var id = url.match(mal.comicIdRegex);
        return id !== null ? id[1] : id;
    },

    addOptionsButton: function() {
        var toolbox = querySel('#rightcontentunder'),
            btn = createElem('a', {
                href: 'javascript:void(0)'
            });
        btn.textContent = 'Open BMF Options';
        btn.addEventListener('click', function() {
            GM_config.open();
        }, false);
        toolbox.insertBefore(btn, toolbox.lastElementChild);
    }
};

mal.p_comic = {
    main: function() {
        if (GM_config.get('i_mu_mal')) {
            this.addInterlinkBtn('mu', 'Search in MangaUpdates');
        }
        if (GM_config.get('i_batoto_mal')) {
            this.addInterlinkBtn('batoto', 'Search in Batoto');
        }
    },

    addInterlinkBtn: function(target, title) {
        var comicTitle = querySel('#contentWrapper h1 span').previousSibling,
            comicName = comicTitle.textContent,
            button = createImgBtn(title, 'bmf_interlink_btn', getResource(target + '_icon'));
        comicTitle.parentNode.insertBefore(button, comicTitle);
        interlinks.getUrl(target, comicName, location.href, function(url) {
            button.href = url;
        });
        button.addEventListener('click', function(event) {
            if (!event.ctrlKey || !event.shiftKey) return;
            event.preventDefault();
            interlinks.saveFromPrompt(location.href, target);
        }, false);
    }
};


//-----------------------------------------------------
// Interlinks between sites
//-----------------------------------------------------

var interlinks = {
    getUrl: function(targetName, comicName, sourceUrl, callback) {
        var target = this.sites[targetName],
            targetId, source, sourceName, sourceId, searchRequest, savedUrl, temporalUrl;
        if (sourceUrl) {
            for (sourceName in this.sites) {
                source = this.sites[sourceName];
                if (source.urlRegex.test(sourceUrl)) {
                    sourceId = source.getId(sourceUrl);
                    if (sourceId) {
                        savedUrl = this.getSaved(sourceName, sourceId, targetName);
                        if (savedUrl) {
                            callback(savedUrl, 'saved');
                            return;
                        }
                    }
                    break;
                }
            }
        }
        if (!savedUrl) {
            if (GM_config.get('i_searchEngine') === 'Google') {
                searchRequest = googleRequest;
            } else if (GM_config.get('i_searchEngine') === 'DuckDuckGo') {
                searchRequest = duckRequest;
            }
            temporalUrl = searchRequest(comicName, target.queryUrl, function(res) {
                if (res.success && res.finalUrl.indexOf(target.queryUrl) !== -1) {
                    callback(res.finalUrl, 'finalUrl');
                    targetId = target.getId(res.finalUrl);
                    if (GM_config.get('i_saveUrls') && sourceId && targetId) {
                        interlinks.save(sourceName, sourceId, targetName, targetId);
                    }
                } else if (res.finalUrl !== 'not supported') {
                    callback(res.searchUrl, 'search');
                }
            });
            callback(temporalUrl, 'temporal');
        }
    },

    getSaved: function(sourceName, sourceId, targetName) {
        var key = 'interlinks_' + sourceName + '->' + targetName,
            stored = getStorage(key),
            targetId;
        if (!stored) {
            return false;
        }
        targetId = stored[sourceId];
        if (targetId) {
            return this.sites[targetName].comicUrl.replace('$comicId$', targetId);
        }
        return false;
    },

    save: function(sourceName, sourceId, targetName, targetId) {
        var key = 'interlinks_' + sourceName + '->' + targetName,
            stored = getStorage(key);
        if (!stored) {
            stored = {};
        }
        if (targetId === false) {
            delete stored[sourceId];
        } else {
            stored[sourceId] = targetId;
        }
        setStorage(key, stored);
    },

    saveFromPrompt: function(sourceUrl, targetName) {
        var sourceName, source, sourceId, userUrl, target, targetId;
        for (sourceName in this.sites) {
            source = this.sites[sourceName];
            if (source.urlRegex.test(sourceUrl)) {
                sourceId = source.getId(sourceUrl);
                break;
            }
        }
        if (!sourceId) {
            alert('Sorry. Couldn\'t get the ID for the comic here.');
            return;
        }
        userUrl = prompt('Write the URL for ' + targetName.toUpperCase() +
            ' (leave empty to remove):');
        if (userUrl === null) return;
        userUrl = userUrl.trim();
        if (userUrl === '') {
            this.save(sourceName, sourceId, targetName, false);
            alert('Removed successfully.');
            return;
        }
        target = this.sites[targetName];
        if (!target.urlRegex.test(userUrl)) {
            alert('Doesn\'t look like a valid URL for ' + targetName.toUpperCase() + '.');
            return;
        }
        targetId = this.sites[targetName].getId(userUrl);
        if (!targetId) {
            alert('Sorry. Couldn\'t get the ID for the comic.');
            return;
        }
        this.save(sourceName, sourceId, targetName, targetId);
        alert('Saved successfully.');
    },

    sites: {
        batoto: {
            urlRegex: /(bato\.to)|(batoto\.net)/i,
            getId: batoto.getComicId,
            comicUrl: 'http://bato.to/comic/_/comics/-r$comicId$',
            queryUrl: 'bato.to/comic/_/'
        },
        mu: {
            urlRegex: /mangaupdates\.com/i,
            getId: mu.getComicId,
            comicUrl: 'https://www.mangaupdates.com/series.html?id=$comicId$',
            queryUrl: 'mangaupdates.com/series.html?'
        },
        mal: {
            urlRegex: /myanimelist\.net/i,
            getId: mal.getComicId,
            comicUrl: 'http://myanimelist.net/manga/$comicId$',
            queryUrl: 'myanimelist.net/manga/'
        }
    }
};


//-----------------------------------------------------
// CSS
//-----------------------------------------------------

var CSS = {
    load: function(site) {
        if (!document.head) {
            setTimeout(function() {
                CSS.load(site);
            }, 1);
            return;
        }
        GM_addStyle(this[site]());
    },

    batoto: function() {
        var css = [
            // various
            '.bmf_following_icon {',
                'vertical-align: top;',
                'margin-left: 1px; }',

            '.general_box.clearfix img {',
                'vertical-align: bottom; }',

            '#pu_____hover___comicPopup_inner .ipsBox td {',
                'padding: 4px; }',

            '#pu_____hover___comicPopup_inner .ipsBox td span ~ span {',
                'display: none !important; }',

            '#pu_____hover___comicPopup_inner .ipsBox tr:last-child {',
                'display: none; }',

            'body table.ipb_table td {',
                'padding: ' + GM_config.get('batoto_followRowsHeight') + '; }',

            'body .chapters_list td {',
                'padding: 3px 3px 4px 3px !important; }',

            // search
            '.bmf_hide_matches .bmf_match {',
                'display: none; }',

            '.bmf_mismatch .bmf_following_icon {',
                'display: none; }',

            '#bmf_upd_follows_btn {',
                'position: absolute;',
                'top: 10px;',
                'left: 110px;',
                'font-weight: normal; }',

            // old follows
            '.bmf_sort_btn {',
                'float: right;',
                'margin : 0 0 0 5px !important;',
                'min-width: 50px !important;',
                'height: 26px !important;',
                'line-height: 26px !important;',
                'padding: 0 11px !important;',
            batoto.getTheme() === 'sylo' ? 'font-size: 11px;' : '',
                'font-family: helvetica,arial,sans-serif; }',

            '.clearfix > h3.maintitle {',
                'line-height: 26px;',
                'padding-top: 11px;',
                'padding-bottom: 11px; }',

            '.bmf_hide_read .bmf_read, ',
                '.bmf_hide_reading .bmf_reading, ',
                '.bmf_hide_noreads .bmf_noreads {',
                'display: none; }',

            '.bmf_read sup {',
                'display: none; }',

            '.bmf_info_row > td {',
                'padding: 0px !important;',
                'border: none !important; }',

            '.bmf_info_row .ipsBox {',
                'background-color: transparent; }',

            '.bmf_info_button {',
                'padding: 0px 8px;',
                'margin-right: 3px;',
                'background-repeat: no-repeat;',
                'background-position: center;',
                'background-image: url(' + getResource('batoto_infoIcon') + '); }',

            '#bmf_total_follows {',
                'font-size: 12px;',
                'padding-left: 8px;',
                'color: ' + GM_config.get('batoto_totalTextColor') + ';',
                'display: inline;',
                'vertical-align: baseline;',
                'line-height: 12px; }',

            'table .row0 ~ .row0 > td:last-child > div, table .row1 ~ .row1 > td:last-child > div {',
                'vertical-align: -3px; }',

            'table .row0 + .row0 > td > div, table .row1 + .row1 > td > div {',
                'vertical-align: -5px; }',

            // comic page
            '.bmf_interlink_btn {',
                'display: inline-block !important;',
                'width: 28px;',
                'height: 28px !important;',
                'vertical-align: -2px !important;',
                'text-align: center;',
                'margin: 0px 8px 0px 0px !important;',
                'padding: 0px !important; }',

            '.bmf_interlink_btn img {',
                'padding: 6px;',
                'margin: 0px !important;',
                'vertical-align: -2px !important; }',

            '#bmf_show_recent_btn {',
                'width: 100%;',
                'margin-top: 5px;',
                'position: absolute;',
                'display: flex; }',

            '#bmf_show_recent_btn a {',
                'margin: 0 auto;',
                'font-weight: bold;',
                'font-size: 12px; }'

        ].join('');

        if (GM_config.get('batoto_hideBloodHeader') && batoto.getTheme() === 'blood') {
            css += [

                '#branding { ',
                    'position: absolute !important; } '

            ].join('');
        }

        var customPrimary = readCookie('customPrimary'),
            customSecondary = readCookie('customSecondary'),
            customPattern = readCookie('customPattern');
        if (batoto.getTheme() === 'subway' && (customPrimary || customSecondary || customPattern)) {
            if (customPrimary) {
                css += ".cpe #primary_nav, .cpe .maintitle, .cpe #community_app_menu > li.active > a, .cpe .col_c_icon img[src*='f_'], .cpe .f_icon, .cpe .topic_buttons li a, .cpe .pagination .pages li.active, .cpe #primary_extra_menucontent, .cpe #more_apps_menucontent, .cpe .post_block h3, .cpe .mini_pagination a, .cpe .user_controls li a, .cpe #vnc_filter_popup_close, .cpe #search .submit_input, .cpe .col_f_icon img, .cpe .ipsBadge_green, body.cpe #logo, .cpe #themeToggle, .cpe #primary_extra_menucontent, .cpe #more_apps_menucontent, .cpe .submenu_container{ background-color: #" + customPrimary + ';}.cpe .forum_name a, .cpe .subforums a{ color: #' + customPrimary + ';}';
            }
            if (customSecondary) {
                css += 'body.cpe, #themeEditor #editPattern span, .cpe #socialLinks li, .cpe #secondary_navigation, .cpe ul.post_controls a:hover, .cpe .answerBadgeInPost, .cpe .ipsLikeButton.ipsLikeButton_enabled, .input_submit, .cpe .ipsTag{ background-color: #' + customSecondary + ';}.cpe ul.post_controls a{ color: #' + customSecondary + ';}';
            }
            if (customPattern) {
                css += 'body.cpe, .cpe #logo, .cpe #primary_nav, .cpe #community_app_menu > li.active > a, .cpe #themeToggle {background-image: url(http://bato.to/forums/public/style_images/subway/patterns/' + customPattern + ');}';
            }
        }

        return css;
    },

    mu: function() {
        var css = [
            // comic page
            '.bmf_interlink_btn {',
                'background: #e4e4e4;',
                'background: linear-gradient(to bottom ,#f6f6f6, #d7d7d7);',
                'box-shadow: -1px 1px 0px 0px #dfdcdc inset, 1px -1px 0px 0px #bfbfbf inset;',
                'border-radius: 4px;',
                'display: inline-block;',
                'width: 28px;',
                'height: 28px;',
                'margin-right: 5px;',
                'vertical-align: -6px;',
                'text-align: center; }',

            '.bmf_interlink_btn img {',
                'padding: 5px;',
                'vertical-align: 0;',
                'margin-top: 1px; }'

        ].join('');

        return css;
    },

    mal: function() {
        var css = [
            // comic page
            '.bmf_interlink_btn {',
                'background: #e4e4e4;',
                'background: linear-gradient(to bottom ,#f6f6f6, #d7d7d7);',
                'box-shadow: -1px 1px 0px 0px #dfdcdc inset, 1px -1px 0px 0px #bfbfbf inset;',
                'border-radius: 4px;',
                'display: inline-block;',
                'width: 28px;',
                'height: 28px;',
                'margin-right: 5px;',
                'vertical-align: -6px;',
                'text-align: center; }',

            '.bmf_interlink_btn img {',
                'padding: 5px;',
                'vertical-align: 0;',
                'margin-top: 1px; }'

        ].join('');

        return css;
    }
};


//-----------------------------------------------------
// Utility functions
//-----------------------------------------------------

function googleRequest(query, queryUrl, callback) {
    var encodedQuery = query.toLowerCase().replace(/\||-|~|"|\*|:/g, ' ').replace(/\s+/g, ' '),
        encodedQuery = encodeURIComponent(encodedQuery),
        encodedQueryUrl = encodeURIComponent(queryUrl),
        temporalUrl = 'https://www.google.com/webhp?#btnI=I&q=' + encodedQuery + '&sitesearch=' + encodedQueryUrl,
        requestUrl = 'https://www.google.com/search?btnI=I&q=' + encodedQuery + '&sitesearch=' + encodedQueryUrl,
        searchUrl = 'https://www.google.com/search?q=' + encodedQuery + '&sitesearch=' + encodedQueryUrl;
    GM_xmlhttpRequest({
        method: 'HEAD',
        url: requestUrl,
        headers: {
            referer: 'https://www.google.com/'
        },
        onload: function(response) {
            var finalUrl = response.finalUrl;
            callback({
                'success': (finalUrl && finalUrl.indexOf('https://www.google.com/') !== 0),
                'query': query,
                'queryUrl': queryUrl,
                'finalUrl': finalUrl || 'not supported',
                'searchUrl': searchUrl
            });
        }
    });
    return temporalUrl;
}

function duckRequest(query, queryUrl, callback) {
    var encodedQuery = query.toLowerCase().replace(/\\|"|!|-|:/g, ' ').replace(/\s+/g, ' '),
        encodedQuery = encodeURIComponent(encodedQuery),
        encodedQueryUrl = encodeURIComponent(queryUrl),
        requestUrl = 'https://duckduckgo.com/?kp=-1&q=%5C' + encodedQuery + '+site%3A' + encodedQueryUrl,
        searchUrl = 'https://duckduckgo.com/?kp=-1&q=' + encodedQuery + '+site%3A' + encodedQueryUrl;
    GM_xmlhttpRequest({
        method: 'GET',
        url: requestUrl,
        onload: function(response) {
            var finalUrl = response.responseText.match(/&uddg=(.+?)'/);
            finalUrl = decodeURIComponent(finalUrl[1]);
            callback({
                'success': (finalUrl && finalUrl.indexOf('https://duckduckgo.com/') !== 0),
                'query': query,
                'queryUrl': queryUrl,
                'finalUrl': finalUrl || false,
                'searchUrl': searchUrl
            });
        }
    });
    // temporalUrl = requestUrl
    return requestUrl;
}

function getStorage(key, GM) {
    var value = GM ? GM_getValue('BMF_' + key) : localStorage.getItem('BMF_' + key);
    if (value !== undefined && value !== null) {
        value = JSON.parse(value);
    }
    return value;
}

function setStorage(key, value, GM) {
    if (GM) {
        GM_setValue('BMF_' + key, JSON.stringify(value));
    } else {
        localStorage.setItem('BMF_' + key, JSON.stringify(value));
    }
}

function decodeHtmlEntity(encoded) {
    var div = document.createElement('div');
    div.innerHTML = encoded;
    return div.firstChild.nodeValue;
}

function createImgBtn(title, className, src, href) {
    var button = createElem('a', {
        title: title || '',
        class: className || '',
        href: href || 'javascript:void(0)'
    });
    var img = createElem('img', {
        src: src,
        alt: ''
    });
    button.appendChild(img);
    return button;
}

function querySel(selector, doc) {
    doc = doc || document;
    return doc.querySelector(selector);
}

function querySelAll(selector, doc, toArray) {
    doc = doc || document;
    var nodes = doc.querySelectorAll(selector);
    return toArray ? [].slice.call(nodes, 0) : nodes;
}

function createElem(tag, attributes, doc) {
    doc = doc || document;
    var elem = doc.createElement(tag);
    for (var attr in attributes) {
        elem.setAttribute(attr, attributes[attr]);
    }
    return elem;
}

function createElemHTML(html, doc) {
    doc = doc || document;
    var elem = doc.createElement('div');
    elem.innerHTML = html;
    return elem.firstElementChild;
}

function toCharObject(stringArray) {
    var object = {},
        firstChar;
    for (var i = 0, len = stringArray.length; i < len; i++) {
        firstChar = stringArray[i].charAt(0);
        object[firstChar] = object[firstChar] || [];
        object[firstChar].push(stringArray[i]);
    }
    return object;
}

function inCharObject(charObject, string) {
    var charArray = charObject[string.charAt(0)] || [];
    return (charArray.indexOf(string) !== -1);
}

function ready(callback) {
    ready.queue = ready.queue || [];
    ready.fired = ready.fired || false;

    if (ready.fired) {
        setTimeout(callback, 0);
        return;
    } else if (document.readyState === 'complete') {
        ready.fired = true;
        setTimeout(callback, 0);
        return;
    }
    if (!ready.whenReady) {
        ready.whenReady = function() {
            if (!ready.fired) {
                ready.fired = true;
                for (var i = 0; i < ready.queue.length; i++) {
                    ready.queue[i].call(window);
                }
                ready.queue = [];
            }
        };
        document.addEventListener('DOMContentLoaded', ready.whenReady, false);
        document.onreadystatechange = function() {
            if (document.readyState === 'complete') {
                ready.whenReady();
            }
        };
    }
    ready.queue.push(callback);
}

function reload() {
    Window.stop();
    setTimeout(function() {
        location.reload();
    }, 0);
}

function createCookie(name, value, days) {
    if (days) {
        var date = new Date();
        date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
        var expires = '; expires=' + date.toGMTString();
    } else var expires = '';
    document.cookie = name + '=' + value + expires + '; path=/';
}

function readCookie(name) {
    var nameEQ = name + '=';
    var ca = document.cookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
}

function eraseCookie(name) {
    createCookie(name, '', -1);
}

function getResource(key) {
    var res = getStorage('resource_' + key, true),
        config = GM_config.get(key);
    if ((!res || !res.base64) && config) {
        updateResource(key, config);
        return config;
    } else if (!config) {
        console.log('BMF: No resource nor config for:' + key);
        return null;
    }
    return res.base64;
}

function updateResource(key, url) {
    var res = getStorage('resource_' + key, true);
    if (GM_xmlhttpRequest && (!res || !res.base64 || res.url !== url)) {
        GM_xmlhttpRequest({
            method: 'GET',
            url: url,
            overrideMimeType: 'text/plain; charset=x-user-defined',
            onload: function(response) {
                res = {
                    url: url,
                    base64: 'data:image/png;base64,' + btoa(response.responseText.replace(/[\u0100-\uffff]/g, function(c) {
                        return String.fromCharCode(c.charCodeAt(0) & 0xff);
                    }))
                };
                setStorage('resource_' + key, res, true);
            },
            onfail: function(response) {
                console.log('BMF: Could\'nt update resource:', key, url, response);
            }
        });
    }
}


//-----------------------------------------------------
// Run Script (GM_config and then BMF)
//-----------------------------------------------------

GM_config.init({
    id: 'BMF_Options',
    title: 'BMF Options',
    css: '.section_desc { text-align: left !important; } .section_desc, .section_header { margin: 18px 0 6px !important; }',
    events: {
        save: function() {
            // update icons
            for (var id in GM_config.fields) {
                if (GM_config.fields[id].settings.label.match(/Icon URL:/)) {
                    updateResource(id, GM_config.get(id), true);
                }
            }
            // update subway theme cookies
            var customPrimary = GM_config.get('batoto_s_customPrimary').trim();
            if (customPrimary !== '') {
                eraseCookie('customPrimary');
                createCookie('customPrimary', customPrimary, 360);
            }
            var customSecondary = GM_config.get('batoto_s_customSecondary').trim();
            if (customSecondary !== '') {
                eraseCookie('customSecondary');
                createCookie('customSecondary', customSecondary, 360);
            }
        }
    },
    fields: {
        // Interlinks
        'i_searchEngine': {
            'label': 'Search Engine:',
            'section': ['Interlinks'],
            'options': [
                    'Google',
                    'DuckDuckGo'
            ],
            'type': 'select',
            'default': 'Google'
        },
        'i_saveUrls': {
            'type': 'checkbox',
            'label': 'Save URLs.',
            'title': 'You can also edit them manually by doing CTRL + SHIFT + CLICK on the buttons',
            'default': true
        },
        'i_batoto_mu': {
            'section': [undefined, 'Add links in the comic pages between:'],
            'label': 'Batoto and Mangaupdates.',
            'type': 'checkbox',
            'default': true
        },
        'i_batoto_mal': {
            'type': 'checkbox',
            'label': 'Batoto and MyAnimeList.',
            'default': true
        },
        'i_mu_mal': {
            'type': 'checkbox',
            'label': 'Mangaupdates and MyAnimeList.',
            'default': true
        },

        //====== Batoto ======
        'batoto_icon': {
            'label': 'Icon URL:',
            'section': ['Batoto'],
            'type': 'text',
            'size': 100,
            'default': 'https://bato.to/forums/favicon.ico'
        },
        'batoto_infoIcon': {
            'label': 'Info Icon URL:',
            'type': 'text',
            'size': 100,
            'default': 'https://bato.to/forums/public/style_images/master/information.png'
        },
        'batoto_followingIcon': {
            'label': 'Following Icon URL:',
            'type': 'text',
            'size': 100,
            'default': 'https://bato.to/forums/public/style_images/master/star.png'
        },
        'batoto_comicsPopupDelay': {
            'label': 'Comic popup/hovercard delay (ms):',
            'title': 'Opens while hovering a URL to a comic page.',
            'type': 'unsigned int',
            'size': 5,
            'default': 1000
        },
        'batoto_defaultToOldFollows': {
            'type': 'checkbox',
            'label': "Set 'Old Follows' as default.",
            'title': 'Replace links in the upper toolbar.',
            'default': true
        },
        // Subway Theme
        'batoto_s_customPrimary': {
            'label': 'Primary color (Hex):',
            'section': [undefined, 'Subway Theme:'],
            'type': 'text',
            'size': 10,
            'default': '59b6dd'
        },
        'batoto_s_customSecondary': {
            'label': 'Secondary color (Hex):',
            'type': 'text',
            'size': 10,
            'default': '333535'
        },
        // Blood Theme
        'batoto_hideBloodHeader': {
            'section': [undefined, 'Blood Theme:'],
            'type': 'checkbox',
            'label': 'Make the header scroll with the page.',
            'default': true
        },
        // Home Page
        'batoto_followingIcon_home': {
            'section': [undefined, 'Home:'],
            'type': 'checkbox',
            'label': 'Add icon to follows.',
            'default': true
        },
        // Old Follows Page
        'batoto_autoSort': {
            'section': [undefined, 'Old Follows:'],
            'type': 'checkbox',
            'label': 'Sort follows automatically.',
            'default': true
        },
        'batoto_defaultSort': {
            'label': 'Default sort:',
            'options': [
                    "Last Update's Date",
                    "Last Update's Ch",
                    "Last Read's Date",
                    "Last Read's Ch",
                    'Unread Chs',
                    'Title'
            ],
            'type': 'select',
            'default': "Last Update's Date"
        },
        'batoto_adaptSortButton': {
            'type': 'checkbox',
            'label': 'Adapt the color of the sorting buttons to the theme (Subway).',
            'default': true
        },
        'batoto_addInfoBtns': {
            'type': 'checkbox',
            'label': 'Add info button to follows.',
            'default': true
        },
        'batoto_showTotalFollows': {
            'type': 'checkbox',
            'label': 'Add text showing the total of follows.',
            'default': true
        },
        'batoto_totalTextColor': {
            'label': 'Text color of the total of follows:',
            'type': 'text',
            'size': 15,
            'default': '#EEEEEE'
        },
        'batoto_addBoxToNewFollows': {
            'type': 'checkbox',
            'label': "Add box linking to 'New Follows'.",
            'default': true
        },
        'batoto_categorizeFollows': {
            'type': 'checkbox',
            'label': 'Categorize follows.',
            'default': true
        },
        'batoto_followRowsHeight': {
            'label': 'Height of the rows of follows.',
            'type': 'text',
            'size': 4,
            'default': '12px'
        },
        // Comic Search Page
        'batoto_followingIcon_search': {
            'section': [undefined, 'Comic Search:'],
            'type': 'checkbox',
            'label': 'Add icon to follows.',
            'default': true
        },
        // Comic Directory Page
        'batoto_comicsPopup_comicDir': {
            'section': [undefined, 'Comic Directory:'],
            'type': 'checkbox',
            'label': 'Enable comic info popup/hovercard.',
            'title': 'Opens while hovering a URL to a comic page.',
            'default': true
        },
        // Comic Page
        'batoto_hyperlinkDesc': {
            'section': [undefined, 'Comic:'],
            'type': 'checkbox',
            'label': 'Hyperlink description.',
            'default': true
        },
        'batoto_showMoreRecentBtn': {
            'type': 'checkbox',
            'label': "Add a 'Show more' button under 'Recently Added Comics'.",
            'default': true
        },
        'batoto_comicsPopup_comic': {
            'type': 'checkbox',
            'label': 'Enable comic info popup/hovercard.',
            'title': 'Opens while hovering a URL to a comic page.',
            'default': true
        },
        'batoto_addSpoilersType': {
            'type': 'checkbox',
            'label': 'Add text indicating the content of each spoiler (Text/Image).',
            'default': true
        },
        'batoto_autoOpenImgSpoilers': {
            'type': 'checkbox',
            'label': 'Auto open image spoilers.',
            'default': true
        },
        'batoto_fixPageTitle': {
            'type': 'checkbox',
            'label': 'Shorter comic page title.',
            'title': "Replaces '- Scanlations - Comic - Comic Directory - Batoto - Batoto Home.' with '- Batoto'.",
            'default': true
        },
        // Group Page
        'batoto_comicsPopup_group': {
            'section': [undefined, 'Group:'],
            'type': 'checkbox',
            'label': 'Enable comic info popup/hovercard.',
            'title': 'Opens while hovering a URL to a comic page.',
            'default': false
        },
        // Forums
        'batoto_comicsPopup_forums': {
            'section': [undefined, 'Forums:'],
            'type': 'checkbox',
            'label': 'Enable comic info popup/hovercard.',
            'title': 'Opens while hovering a URL to a comic page.',
            'default': true
        },

        //====== MangaUpdates ======
        'mu_icon': {
            'label': 'Icon URL:',
            'section': ['MangaUpdates'],
            'type': 'text',
            'size': 100,
            'default': 'https://www.mangaupdates.com/favicon.ico'
        },

        //====== MyAnimeList ======
        'mal_icon': {
            'label': 'Icon URL:',
            'section': ['MyAnimeList'],
            'type': 'text',
            'size': 100,
            'default': 'http://cdn.myanimelist.net/images/faviconv5.ico'
        }
    }
});


var Window = this.unsafeWindow || unsafeWindow || window,
    host = location.host,
    path = location.pathname;

if (host === 'bato.to') {
    CSS.load('batoto');
    batoto.init();
} else if (host === 'www.mangaupdates.com') {
    CSS.load('mu');
    mu.init();
} else if (host === 'myanimelist.net') {
    CSS.load('mal');
    mal.init();
}


if (typeof GM_registerMenuCommand === 'function') {
    GM_registerMenuCommand('Open BMF Options', function() {
        GM_config.open();
    });
}