Cpt_mathix / MyAnimeList(MAL) - Anime Recommendations Filter

// ==UserScript==
// @name           MyAnimeList(MAL) - Anime Recommendations Filter
// @version        1.1.3
// @description    This script can hide recommendations that you already have on your list/don't have on your list
// @author         Cpt_mathix
// @include        /^https?:\/\/myanimelist\.net\/(anime|manga)\/\d+\/?/
// @include        /^https?:\/\/myanimelist\.net\/(anime|manga)\/\d+\/.+\/?/
// @include        /^https?:\/\/myanimelist\.net\/(anime|manga)\/\d+\/.+\/userrecs/
// @include        *://myanimelist.net/(anime|manga).php?id=*
// @exclude        /^https?:\/\/myanimelist\.net\/(anime|manga)\/[^0-9]+/
// @exclude        /^https?:\/\/myanimelist\.net\/(anime|manga)\/\d+\/.+\/(?!userrecs$)[^\s]/
// @grant          GM_getValue
// @grant          GM_setValue
// @licence        GPL-2.0+; http://www.gnu.org/licenses/gpl-2.0.txt
// @namespace      https://greasyfork.org/users/16080
// ==/UserScript==

var href = document.location.href;
var page = /^http.*:\/\/myanimelist\.net\/manga*/.test(href) ? 'manga' : 'anime';

var userrecs = href.indexOf('userrecs') > -1;
var version = '1.1.3';

// get user
var user = document.getElementsByClassName('header-profile-link')[0];
if (user) {
    user = user.textContent;
    init();
} else {
    console.log('Not logged in (Anime Recommendations Filter)');
}

function init() {
    // get header
    var AnchorLink;
    var allTextareas = document.getElementsByTagName('H2');
    for(var element in allTextareas) {
        if(allTextareas[element].textContent.indexOf("ecommendation") > -1) {
            AnchorLink = allTextareas[element];
            break;
        }
    }

    if (AnchorLink !== null) {
        addCheckboxes(AnchorLink);
        AnchorLink.id = "RecHeader";
    }

    if (userrecs) {
        startFilter1(getSetting('Rec'), getSetting('Rec2'));
    } else {
        console.log('Running script: Anime Recommendations Filter');
        if (page === "manga") {
            loadRSS(user, "manga", "rm");
        } else {
            loadRSS(user, "anime", "rw");
        }
    }
}

function runScript() {
    scrollFunction();
    startFilter2(getSetting('Rec'), getSetting('Rec2'));
}

function addCheckboxes(AnchorLink) {
    var checkbox1 = document.createElement('input');
    var checkbox2 = document.createElement('input');
    checkbox1.type = "checkbox";
    checkbox2.type = "checkbox";
    checkbox1.className = "checkbox";
    checkbox2.className = "checkbox";
    checkbox1.name = "Rec";
    checkbox2.name = "Rec";
    checkbox1.id = "Rec";
    checkbox2.id = "Rec2";
    checkbox1.title = "Hide entries on your list";
    checkbox2.title = "Hide entries that are not on your list";
    checkbox1.checked = getSetting("Rec");
    checkbox2.checked = getSetting("Rec2");
    checkbox1.addEventListener('change', function(e) {
        addClickEvent(e);
    });
    checkbox2.addEventListener('change', function(e) {
        addClickEvent(e);
    });
    AnchorLink.appendChild(checkbox1);
    AnchorLink.appendChild(checkbox2);
}

function scrollFunction() {
    var right = document.querySelector('#' + page + '_recommendation > div.btn-anime-slide-side.right > span');
    var left = document.querySelector('#' + page + '_recommendation > div.btn-anime-slide-side.left > span');

    if (right !== null) {
        var elCloneR = right.cloneNode(true);
        right.parentNode.replaceChild(elCloneR, right);
        elCloneR.onclick = function() {
            scrollRight();
        };
    }

    if (left !== null) {
        var elCloneL = left.cloneNode(true);
        left.parentNode.replaceChild(elCloneL, left);
        elCloneL.onclick = function() {
            scrollLeft();
        };
    }
}

function scrollRight() {
    var ul = document.querySelector('#' + page + '_recommendation > div.anime-slide-outer > ul');
    var li = ul.querySelectorAll('li:not(.hidden):not(.off)');

    if (li.length > 7) {
        for(var i = 0; i < 7; i++) {
            if (typeof jQuery == 'undefined') {
                li[i].setAttribute('style', 'display:none !important');
            } else {
                $(li[i]).animate({width: 'toggle'}, "fast");
            }
            li[i].classList.add('off');
        }
    }
}

function scrollLeft() {
    var ul = document.querySelector('#' + page + '_recommendation > div.anime-slide-outer > ul');
    var li = ul.querySelectorAll('li.off:not(.hidden)');

    if (li.length > 0) {
        for(var i = li.length - 1; i >= li.length - 7 && i >= 0; i--) {
            if (typeof jQuery == 'undefined') {
                li[i].setAttribute('style', 'display:inline-block; width: 90px;');
            } else {
                $(li[i]).animate({width: 'toggle'}, "fast");
            }
            li[i].classList.remove('off');
        }
    }
}

function startFilter1(conditionEdit, conditionAdd) {
    // get Anime/Manga Entries on current page
    var allElements;
    allElements = document.evaluate(
        '//*[@class="borderClass"]/table/tbody/tr/td[2]/div[2]/a[2]',
        document,
        null,
        XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
        null);

    for (var i = 0; i < allElements.snapshotLength; i++) {
        var EditLink = allElements.snapshotItem(i);
        if (conditionEdit && EditLink.className.indexOf('button_edit') > -1) {
            EditLink.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.style.display="none";
        } else if (conditionAdd && EditLink.className.indexOf('button_add') > -1) {
            EditLink.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.style.display="none";
        } else {
            EditLink.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.style.display="";
        }
    }

}

function startFilter2(conditionNotOnList, conditionOnList) {
    // get Anime/Manga Entries on current page
    var allElements;
    allElements = document.evaluate(
        '//*[@id="' + page + '_recommendation"]/div[3]/ul/li[*]',
        document,
        null,
        XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
        null);

    var list = /^http.*:\/\/myanimelist\.net\/manga*/.test(document.location.href) ? getUserList("manga") : getUserList("anime");
    for (var i = 0; i < allElements.snapshotLength; i++) {
        var linkEl = allElements.snapshotItem(i).firstChild;
        var href = linkEl.href;
        var id = href.match(/\d+/g);
        var self = document.location.href.match(/\d+/g)[0];

        if(linkEl.parentNode.classList.contains("off")) {
            linkEl.parentNode.classList.remove("off");
            linkEl.parentNode.setAttribute('style', 'display:inline-block; width: 90px;');
        }

        if (conditionNotOnList) {
            if (self != id[0] && haveListHit(list, id[0])) {
                linkEl.parentNode.setAttribute('style', 'display:none !important');
                linkEl.parentNode.classList.add("hidden");
            } else if (id[1] !== undefined && self != id[1] && haveListHit(list, id[1])) {
                linkEl.parentNode.setAttribute('style', 'display:none !important');
                linkEl.parentNode.classList.add("hidden");
            } else {
                linkEl.parentNode.setAttribute('style', 'display:inline-block; width: 90px;');
                linkEl.parentNode.classList.remove("hidden");
            }
        } else if (conditionOnList) {
            if (self != id[0] && !haveListHit(list, id[0])) {
                linkEl.parentNode.setAttribute('style', 'display:none !important');
                linkEl.parentNode.classList.add("hidden");
            } else if (id[1] !== undefined && self != id[1] && !haveListHit(list, id[1])) {
                linkEl.parentNode.setAttribute('style', 'display:none !important');
                linkEl.parentNode.classList.add("hidden");
            } else {
                linkEl.parentNode.setAttribute('style', 'display:inline-block; width: 90px;');
                linkEl.parentNode.classList.remove("hidden");
            }
        } else {
            linkEl.parentNode.setAttribute('style', 'display:inline-block; width: 90px;');
            linkEl.parentNode.classList.remove("hidden");
        }
    }
}

function haveListHit(list, id) {
    return list[id];
}

function loadRSS(user, type, query) {
    makeRequest('GET', '/rss.php?type=' + query + '&u=' + user).then(function(rss) {
        var lastUpdate = rss.getElementsByTagName('pubDate')[0].textContent;
        var cachedDate = getCacheDate(type + "rss");

        if (lastUpdate == cachedDate) {
            console.log('Processing cached ' + type + 'list...');
            runScript();
        } else {
            console.log('Cached ' + type + ' data not up to date!');
            setCacheDate(type + "rss", lastUpdate);

            loadUserList(user, type);
        }
    }).catch(function(err) {
        console.log(err);
        console.log(type + ' RSS feed failed to load');
        var lastUpdate = new Date();
        var cachedDate = new Date(getCacheDate(type + "list"));

        if (lastUpdate - cachedDate < 86400000 && lastUpdate - cachedDate > 0) {
            console.log('Processing cached ' + type + 'list...');
            runScript();
        } else {
            console.log('Cached ' + type + ' data not up to date!');

            loadUserList(user, type);
        }
    });
}

function loadUserList(user, type) {
    makeRequest('GET', '/malappinfo.php?u=' + user + '&status=all&type=' + type + '').then(function (data) {
        var xmlDocument = data;

        // create a list that I can cache
        var rawList = xmlDocument.getElementsByTagName('series_' + type + 'db_id');
        var statusList = xmlDocument.getElementsByTagName('my_status');
        var set = {};
        for (var i = 0; i < rawList.length; i++) {
            set[rawList[i].textContent] = true;
        }

        setCacheDate(type + "list", new Date());
        setUserList(type, set);

        runScript();
    }).catch(function (err) {
        console.log("failed to load list, trying to grab cached list");
        runScript();
    });
}

function makeRequest(method, url) {
    return new Promise(function (resolve, reject) {
        var xhr = new XMLHttpRequest();
        xhr.open(method, url);
        xhr.onload = function () {
            if (this.status >= 200 && this.status < 300) {
                resolve(xhr.responseXML);
            } else {
                reject();
            }
        };
        xhr.onerror = function () {
            reject();
        };
        xhr.send();
    });
}

function getUserList(type) {
    var object = GM_getValue('MAL' + type + 'list');
    if (object)
        return JSON.parse(object);
    else {
        console.log("failed to get lists");
        return false;
    }
}

function setUserList(type, list) {
    GM_setValue('MAL' + type + 'list', JSON.stringify(list));
}

function getCacheDate(type) {
    var object = GM_getValue('CacheDate' + type + version);
    if (object)
        return JSON.parse(object);
    else
        return null;
}

function setCacheDate(type, value) {
    GM_setValue('CacheDate' + type + version, JSON.stringify(value));
}

// Save a setting of type = value (true or false)
function saveSetting(type, value) {
    GM_setValue('MALRec_' + type + version, value);
}

// Get a setting of type
function getSetting(type) {
    var value = GM_getValue('MALRec_' + type + version);
    if (value)
        return value;
    else
        return false;
}

function addClickEvent(e) {
    var clickedCheckbox = e.target;
    var checkboxes = document.getElementsByName('Rec');

    for(var i = 0; i < checkboxes.length; i++) {
        if (checkboxes[i].id != clickedCheckbox.id) {
            checkboxes[i].checked = false;
            saveSetting(checkboxes[i].id, false);
        } else {
            saveSetting(checkboxes[i].id, checkboxes[i].checked);
        }
    }

    if (userrecs)
        startFilter1(getSetting('Rec'), getSetting('Rec2'));
    else
        startFilter2(getSetting('Rec'), getSetting('Rec2'));
}