azazar / Фильтры для ficbook.net

// ==UserScript==
// @name        Фильтры для ficbook.net
// @namespace   ficbook.net.uo1.net
// @description Позоляет убрать раздражающих авторов и прозведения. Фильтровать мелочь и фики с низком количеством лайков. Всё настраивается.
// @include     /^https?:\/\/ficbook.net(/.*|)$/
// @version     1.21
// @grant       none
// @updateURL   https://openuserjs.org/meta/azazar/Фильтры_для_ficbook.net.meta.js
// @require     https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js
// @license     MIT
// ==/UserScript==

var personalInfoValues = null;
var banPrefix = "Убрал пейсателей: ";
var banDelim = ", фики: ";
var banRegex = /Убрал пейсателей: ((?:,?\d+)*)?, фики: ((?:,?\d+)*)?/;

var previouslySerialized = null;

function storeBansInProfile(authors, fics) {
    if (personalInfoValues === null) {
        console.error("Can't store bans in profile, it's not loaded");
        return;
    }

    if (!authors) {
        authors = localStorage.getItem('_userscript_banned_authors');
    }
    if (!fics) {
        fics = localStorage.getItem('_userscript_banned_fics');
    }

    if (typeof authors === 'object') {
        authors = authors.join(',');
    }

    if (typeof fics === 'object') {
        fics = fics.join(',');
    }

    var serialized = banPrefix + authors + banDelim + fics;

    if (previouslySerialized === serialized) {
        previouslySerialized = serialized;
        return;
    }

    if (banRegex.test(personalInfoValues.about_myself)) {
        personalInfoValues.about_myself = personalInfoValues.about_myself.replace(banRegex, serialized);
    }
    else {
        personalInfoValues.about_myself += serialized;
    }

    $.post('https://ficbook.net/home/personal_info', personalInfoValues);
}

function applyFicBookFiltering() {
    function eraseCookie(name) {
        var expires = '';

        var date = new Date();
        date.setTime(date.getTime() + (10 * 365 * 24 * 60 * 60 * 1000));
        expires = '; expires=' + date.toGMTString();

        document.cookie = name + '=' + expires + '; path=/';
    }

    function writeStorage(name, value, days) {

        if (typeof window.localStorage !== 'undefined') {
            localStorage.setItem(name, value);

            eraseCookie(name);

            storeBansInProfile();

            return;
        }

        var expires;

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

    function createStorageWriteCode(name, value, days) {
        var expires;

        if (days) {
            var date = new Date();
            date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
            expires = '; expires=' + date.toGMTString();
        } else {
            expires = '';
        }

        var code;

        code = "{\n"
            + "\tk = " + JSON.stringify(name) + ";\n"
            + "\tv = " + JSON.stringify(value) + ";\n"
            + "\tif(typeof window.localStorage !== 'undefined') {\n"
            + "\t\tlocalStorage.setItem(k, v);\n"
            + "\t} else {\n"
            + "\t\tdocument.cookie = k + '=' + v + '" + expires + "; path=/';\n"
            + "\t}\n"
            + "}\n";

        return code;
    }

    function readStorage(name) {

        if (typeof window.localStorage !== 'undefined') {
            var value = localStorage.getItem(name);

            if (value !== null) {
                return value;
            }
        }

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

    var cookiePrefix = "_userscript_";

    var bannedAuthorsCookieName = cookiePrefix + "banned_authors";
    //eraseCookie(bannedAuthorsCookieName);
    var bannedAuthors = {};
    var bannedAuthorsCookie = readStorage(bannedAuthorsCookieName);

    if (bannedAuthorsCookie !== null) {
        bannedAuthorsCookie.split(",").forEach(function (id) {
            bannedAuthors[id] = true;
        });
    }

    console.log("Banned authors:", bannedAuthors);

    var bannedFicsCookieName = cookiePrefix + "banned_fics";
    //eraseCookie(bannedFicsCookieName);
    var bannedFics = {};
    var bannedFicsCookie = readStorage(bannedFicsCookieName);

    if (bannedFicsCookie !== null) {
        bannedFicsCookie.split(",").forEach(function (id) {
            bannedFics[id] = true;
        });
    }

    console.log("Banned fics:", bannedFics);

    function banAuthor(id) {
        if (bannedAuthors[id]) {
            return;
        }

        bannedAuthors[id] = true;
        if (bannedAuthorsCookie === null) {
            bannedAuthorsCookie = id;
        } else {
            bannedAuthorsCookie += "," + id;
        }

        writeStorage(bannedAuthorsCookieName, bannedAuthorsCookie, 365 * 20);
    }

    function unbanAuthor(id) {
        if (!bannedAuthors[id]) {
            return;
        }

        delete bannedAuthors[id];
        if (bannedAuthorsCookie !== null) {
            bannedAuthorsCookie = Object.keys(bannedAuthors).join(',');
        }

        writeStorage(bannedAuthorsCookieName, bannedAuthorsCookie, 365 * 20);
    }

    function banFic(id) {
        if (bannedFics[id]) {
            return;
        }

        bannedFics[id] = true;
        if (bannedFicsCookie === null) {
            bannedFicsCookie = id;
        } else {
            bannedFicsCookie += "," + id;
        }

        writeStorage(bannedFicsCookieName, bannedFicsCookie, 365 * 20);
    }

    function exportCookieData() {
        return createStorageWriteCode(bannedAuthorsCookieName, bannedAuthorsCookie, 365 * 20) + "\n" + createStorageWriteCode(bannedFicsCookieName, bannedFicsCookie, 365 * 20) + "\n";
    }

    var keepVisible = false;

    if (location.pathname.startsWith("/authors/") || "/home/collections" === location.pathname) {
        keepVisible = true;
    }

    function findBlockRoot(element) {
        while (element !== null && element !== document) {
            if (element.classList.contains('fanfic-inline')) {
                if (element.parentNode === null) {
                    console.log("Orphan node found:", element);
                    return element;
                }

                if (element.parentNode.tagName === 'DIV' && element.parentNode.classList.contains('top-item-row')) {
                    return element.parentNode;
                }

                if (element.parentNode.tagName === 'SECTION')
                {
                    if (element.parentNode.parentNode.tagName === 'TD') {
                        //console.log("FOUND: ", element, "P:", element.parentNode, "PP:", element.parentNode.parentNode); return null;
                        return element.parentNode.parentNode.parentNode;
                    }

                    if (element.parentNode.querySelectorAll(".fanfic-inline").length > 1) {
                        return element;
                    }

                    return element.parentNode;
                }

                if (element.parentNode.tagName === "TD") {
                    return element.parentNode.parentNode;
                }

                return element;
            }

            element = element.parentNode;
        }

        return null;
    }

    var deleted = 0;

    function deleteBanned(element) {
        if (keepVisible) {
            return;
        }

        console.log("Removing HTML section for", element);
        var rootElement = findBlockRoot(element);

        if (rootElement === null) {
            console.log("Can't delete section for ", element);
            return false;
        }

        console.log("Root:", rootElement);
        $(rootElement).remove();
        console.log("Block Removed:", rootElement);

        deleted++;

        return true;
    }

    var nextId = 0;

    function isFilteredPage() {
        return location.pathname !== "/home/favourites" && location.pathname !== "/home/collections" && location.pathname !== "/home/liked_fanfics" && !/^\/collections\/\d+$/.test(location.pathname);
    }

    function htmlApplyBans(addButtons) {
        //var links = document.links;
        var links = document.querySelectorAll("A[HREF]");

        for (var i = 0; i < links.length; i++) {
            var link = links[i];
            var href = link.getAttribute("href");
            if (/^\/authors\/\d+$/.test(href)) {
                if (link.innerText === "Мой профиль") {
                    continue;
                }

                var authorId = href.substring("/authors/".length);

                if (bannedAuthors[authorId]) {
                    console.log("Banned:", authorId);
                    if (isFilteredPage()) {
                        if (deleteBanned(link, authorId)) {
                            continue;
                        }
                    }
                }

                if (addButtons) {
                    if (link.children.length > 0 && link.children.item(0).nodeType === Node.ELEMENT_NODE && link.children.item(0).tagName === "IMG") {
                        continue;
                    }

                    if (link.buttons) {
                        link.buttons.remove();
                    }

                    if (bannedAuthors[authorId]) {
                        link.buttons = $("<span> </span><small>(в топке)</small>").insertAfter($(link)).click({ author: authorId, link: link }, function (event) {
                            if (confirm("Точно вернуть?")) {
                                unbanAuthor(event.data.author);
                                location.reload();
                            }
                            event.preventDefault();
                            return false;
                        }
                        );
                    }
                    else {
                        link.buttons = $("<span> </span><small>(<a href=\"#\">в топку</a>)</small>")
                            .click({ author: authorId, link: link }, function (event) {
                                if (confirm("Точно?")) {
                                    banAuthor(event.data.author);
                                    //deleteBanned(ev.data.link);
                                    htmlApplyBans(false);
                                }
                                event.preventDefault();
                                return false;
                            })
                            .insertAfter($(link));
                    }

                    //console.log("Author ban link added: " + authorId);
                }
            }
            else if (/^\/readfic\/\d+$/.test(href)) {
                var ficId = href.substring("/readfic/".length);

                if (bannedFics[ficId]) {
                    if (isFilteredPage()) {
                        console.log("Banned fic:", ficId);
                        deleteBanned(link);
                        continue;
                    }
                }

                if (addButtons) {

                    var id = "_usid_" + ++nextId;

                    if (link.buttons) {
                        link.buttons.remove();
                    }

                    link.buttons = $("<span> </span><small>(<span id='" + id + "'></span>)</small>").insertAfter($(link));

                    $("<a href=\"#\">в топку</a>")
                        .click({ fic: ficId, link: link }, function (ev) {
                            if (confirm("Точно?")) {
                                banFic(ev.data.fic);
                                //deleteBanned(ev.data.link);
                                htmlApplyBans(false);
                            }
                            ev.preventDefault();
                            return false;
                        })
                        .appendTo($("#" + id));

                    //                    $("<span> </span>").appendTo($("#" + id));
                    //                    $("<a href=\"#\">задвинуть</a>")
                    //                    .click({fic: ficId, link: link}, function (ev) {
                    //                        deleteBanned(ev.data.link);
                    //                        ev.preventDefault();
                    //                    })
                    //                    .appendTo($("#" + id));

                    console.log("Fic ban link added:", link);
                }

            }
        }

    }
    htmlApplyBans(true);

    var searchMinPages = document.querySelector("input[name='pages_min']");
    var searchMinLikes = document.querySelector("input[name='likes_min']");

    var minSizeCookieName = cookiePrefix + "filterMinSize";
    var minLikesCookieName = cookiePrefix + "filterMinLikes";

    if (searchMinPages !== null && searchMinPages.value !== '') {
        writeStorage(minSizeCookieName, searchMinPages.value);
    }
    if (searchMinLikes !== null && searchMinLikes.value !== '') {
        writeStorage(minLikesCookieName, searchMinLikes.value);
    }

    var minSize = parseInt(readStorage(minSizeCookieName));
    if (isNaN(minSize)) {
        minSize = 0;
    }

    var minLikes = parseInt(readStorage(minLikesCookieName));
    if (isNaN(minLikes)) {
        minLikes = 0;
    }

    var filtered = 0;
    var onFiltersApplied = function () { };

    var left = 0;

    function applyFilters() {
        if (!isFilteredPage()) {
            return;
        }

        var links = document.querySelectorAll("A[HREF]");

        var visible = 0, hidden = 0, insertAfter = null, insertIntoNode = null;

        for (var i = 0; i < links.length; i++) {
            var link = links[i];
            var href = link.getAttribute("href");

            if (/^\/readfic\/\d+$/.test(href)) {
                var block = findBlockRoot(link);

                if (block === null) {
                    continue;
                }

                if (insertAfter === null) {
                    insertAfter = $(block).prev();
                    insertIntoNode = block.parentNode;
                }

                if (minLikes > 0) {
                    var countEl = block.querySelector(".count");
                    if (countEl !== null) {
                        var count = countEl.innerText;
                        if (count.substring(0, 1) === "+") {
                            count = count.substring(1);
                        }
                        try {
                            count = parseInt(count);

                            if (count < minLikes) {
                                $(block).hide();
                                hidden++;
                                continue;
                            } else {
                                $(block).show();
                            }
                        } catch (e) {
                            console.log("Error:", e);
                        }
                    }
                }

                if (minSize > 0) {
                    var text = block.innerText;
                    var matches = /[ \t\r\n](\d+) страниц/.exec(text);

                    if (matches !== null && matches.length > 0) {
                        var pages = parseInt(matches[1]);

                        console.log(pages);

                        if (pages < minSize) {
                            $(block).hide();
                            hidden++;
                            continue;
                        } else {
                            $(block).show();
                        }

                    } else {
                        console.log("No page count in text", text);
                    }
                }

                visible++;
            }
        }

        if (hidden > 0 && visible === 0 && insertAfter !== null) {
            if ($("#_userscript_allfiltered").length === 0) {
                var htmlSel = $("<div class='block' style='color:red' id='_userscript_allfiltered'>Всё зафильтровано. Может на другой странице найдётся что-то подходящее...</div>");
                if (insertAfter.length === 0) {
                    htmlSel.appendTo(insertIntoNode);
                }
                else {
                    htmlSel.insertAfter(insertAfter);
                }
            }
        }
        else {
            $("#_userscript_allfiltered").remove();
        }

        left = visible;

        filtered = hidden;
        onFiltersApplied();
    }

    var cpDiv = $("<div><h2>Дополнительные фильтры</h2></div>");
    /*
    $("<A STYLE='display:block; text-decoration: none; float:right; background-color: black; color: white; border-radius: 0.25em; width: 1.85em; height: 1.85em; text-align: center' HREF='#'>x</A>").appendTo($(cpDiv)).click(function (event) {
        $(cpDiv).hide();
        event.preventDefault();
    });
    */
    $("<BR>").appendTo($(cpDiv));
    var statsDiv = $("<DIV></DIV>");
    statsDiv.appendTo(cpDiv);

    var loadingSearchPage = false;
    var loadingUrl = null;

    function loadSearchPage() {
        if (loadingSearchPage) {
            console.log('loading page already');
            return;
        }

        if (location.pathname !== '/find') {
            console.log('not a serp');
            return;
        }

        var ib = document.querySelector('#yandex_rtb_2');
        if (ib === null) {
            var ah = document.querySelectorAll('.pagination-holder');
            ib = ah.length > 0 ? ah[ah.length - 1] : null;
        }

        if (ib === null) {
            console.log('unable to find insert location');
        }

        var a_ = document.querySelectorAll("li:not(.disabled) a>.icon-arrow-right");
        var a = a_[0];
        if (a === null) {
            console.log('no next page');
        }
        a = a.parentNode;
        if (a.tagName === 'A' && a.hasAttribute("href")) {
            var nextLink = a.href;

            if (loadingUrl === nextLink) {
                return;
            }

            for (var i = 0; i < a_.length; i++) {
                var a = a_[i].parentNode;
                a.setAttribute('original-href', a.getAttribute('href'));
                a.removeAttribute('href');
            }

            console.log('Loading extra blocks from', nextLink);

            loadingUrl = nextLink;

            $.get(nextLink, null, function (data, textStatus, jqXHR) {
                var d = new DOMParser().parseFromString(data, "text/html");

                console.log('insert before:', ib, 'in:', ib.parentNode);

                var bs = d.querySelectorAll('.fanfic-thumb-block');

                console.log('blocks on page:', bs.length);

                var scrollY = window.scrollY;

                try {
                    var cc = document.createElement('div');
                    for (var i = 0; i < bs.length; i++) {
                        var b = bs[i];
                        cc.innerHTML = b.outerHTML;
                        var b = cc.childNodes[0];
                        cc.removeChild(b);
                        $(b.outerHTML).insertBefore($(ib));
                        //var n = ib.parentNode.insertBefore(b, ib);
                        console.log('Block added');
                    }

                    window.scrollY = scrollY;
                } catch (e) {
                    loadingSearchPage = false;
                    console.error(e);
                    throw e;
                }

                var nextLink = d.querySelector('li:not(.disabled) a>.icon-arrow-right');
                if (nextLink !== null) {
                    nextLink = nextLink.parentNode;
                }

                a_ = document.querySelectorAll('li:not(.disabled) a>.icon-arrow-right');
                if (a_ !== null && a_.length > 0 && nextLink !== null) {
                    for (var i = 0; i < a_.length; i++) {
                        var a = a_[i].parentNode;
                        a.setAttribute('href', nextLink.getAttribute('href'));
                    }
                }
                else {
                    console.log('Last page');
                    a_ = document.querySelectorAll('li > a > .icon-arrow-right');
                    if (nextLink !== null) {
                        for (var i = 0; i < a_.length; i++) {
                            var a = a_[i].parentNode;
                            nextLink.parentNode.parentNode.setAttribute('class', 'disabled');
                        }
                    }
                }

                htmlApplyBans(true);
                applyFilters();
            });
        }
        loadingSearchPage = false;
    }

    statsDiv.html("-");
    onFiltersApplied = function () {
        statsDiv.html("<div style='padding: 0.5em 0.5em 1.5em 0.5em'>Топка: " + deleted + "<br>Фильтр: " + filtered);

        if (left < 15 && location.pathname === '/find' && document.querySelector('.fanfic-thumb-block') !== null) {
            loadSearchPage();
        }
    };

    var filtersNode = null;

    function addFilterInput(initialValue, onValueChanged, textLabel) {
        if (filtersNode === null) {
            filtersNode = $("<div style='border-left: 5px solid #cab39e; background-color: #eae2d1; padding: 5px 15px;'>");
            filtersNode.appendTo(cpDiv);
        }
        var group = $("<div class='form-group form-group-sm'><label>" + textLabel + "</label><div class='form-inline'><input style='width: 5em' class='form-control short-number-input' id='minLinksFilter' type=number value='" + initialValue + "'>");
        var input = group.find('input');
        //$("<label for='minLinksFilter'>" + textLabel + ":</label>").appendTo(cpDiv);
        group.appendTo(filtersNode);
        input.on("input", function (event) {
            var value;
            try {
                value = parseInt(input.val());

                if (isNaN(value)) {
                    value = 0;
                }
            } catch (e) {
                value = 0;
            }

            onValueChanged(value);

            applyFilters();
        });

        return input;
    }

    var showPanel = !/^\/readfic\/\d+(?:\/\d+)?$/.test(location.pathname);

    if (showPanel && "/home/collections" === location.pathname) {
        showPanel = false;
    }

    if (showPanel) {

        var searchMinPages = document.querySelector("input[name='pages_min']");
        var searchMinLikes = document.querySelector("input[name='likes_min']");

        if (searchMinPages !== null && searchMinLikes !== null) {

            searchMinPages.addEventListener('input', function () {
                minSize = parseInt(searchMinPages.value);
                writeStorage(minSizeCookieName, minSize, 365 * 10);
                applyFilters();
            });

            searchMinLikes.addEventListener('input', function () {
                minLikes = parseInt(searchMinLikes.value);
                writeStorage(minLikesCookieName, minLikes, 365 * 10);
                applyFilters();
            });

        }
        else {
            addFilterInput(minSize, function (value) {
                minSize = value;
                writeStorage(minSizeCookieName, value, 365 * 10);
            }, "Размер");
            addFilterInput(minLikes, function (value) {
                minLikes = value;
                writeStorage(minLikesCookieName, value, 365 * 10);
            }, "Лайки");
        }

        var exportBtn = $("<input type='button' class='btn btn-default btn-block' value='Экспорт'>");
        exportBtn.click(function () {
            exportCookieData();
            exportBtn.hide();
            $('<textarea readonly></textarea>').insertAfter(exportBtn).text(exportCookieData());
        });
        cpDiv.append("<br>");
        exportBtn.appendTo(cpDiv);

        //cpDiv.appendTo("body");
        //cpDiv.attr('style', 'position: fixed; top: 0; right: 0; z-index: 999; background-color: #fff; padding: 0.5em 0.5em 0.5em 0.5em; border-radius: 1em');
        cpDiv.appendTo("#main .content-section");

        /*
        if (location.pathname === "/find") {
            cpDiv.append($("<button>Load</button>").click(function() {
                loadSearchPage();
            }));
        }
        */

    }

    var atBottom = null;
    var bottomResistance = 0;

    if (location.pathname === "/find") {
        window.addEventListener('scroll', function (ev) {
            atBottom = ((window.innerHeight + window.scrollY) >= document.body.offsetHeight);
            if (!atBottom) {
                bottomResistance = 5;
            }
        });

        window.addEventListener('mousewheel', function (ev) {
            //console.log('wheel:', ev);

            if (ev.deltaY <= 0) {
                bottomResistance = 5;
            }

            if (atBottom) {
                bottomResistance--;

                if (bottomResistance <= 0) {
                    bottomResistance = 5;
                    loadSearchPage();
                }
            }
        });
    }

    $('.fanfic-thumb-block-premium').hide();


    applyFilters();

    console.log('My ficbook.net userscript executed, f:', isFilteredPage());
}

/*$.get("https://ficbook.net/home/personal_info", null, function (data, textStatus, jqXHR) {
    var d = new DOMParser().parseFromString(data, "text/html");

    personalInfoValues = {
        'about_myself': d.querySelector('#aboutInput').value,
        'do_save': 'Сохранить+изменения',
        'www': d.querySelector('#wwwInput').value,
        'email': d.querySelector('#emailInput').value,
        'show_email': 'on',
        'icq': d.querySelector('#icqInput').value,
        'support_me': d.querySelector('#supportInput').value,
        'skype': d.querySelector('#skypeInput').value,
    };

    if (!d.querySelector("input[name='show_email']").checked) {
        delete personalInfoValues['show_email'];
    }

    var matches = banRegex.exec(personalInfoValues['about_myself']);

    if (matches) {
        localStorage.setItem('_userscript_banned_authors', matches[1]);
        localStorage.setItem('_userscript_banned_fics', matches[2]);
    }

    console.log('Профиль: ', personalInfoValues);

    setTimeout(applyFicBookFiltering, 1);
}, "text");*/

applyFicBookFiltering();