Anilist VA filter

Filters the list of characters voiced by a VA to show only the characters from anime in your completed and current watchlists.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Anilist VA filter
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Filters the list of characters voiced by a VA to show only the characters from anime in your completed and current watchlists.
// @author       Arunato
// @match        https://anilist.co/*
// @grant        none
// @require      https://code.jquery.com/jquery-3.3.1.min.js
// ==/UserScript==

(function() {
    'use strict';
    var $ = window.jQuery;

    // Retrieves user name
    function getUser(){
        const profileLink = document.querySelector(".links").childNodes[2].href;
        const re = new RegExp("https://anilist.co/user/(.*)/");
        const user = profileLink.match(re)[1];
        return user;
    }

    function getWatchlist(user, status){
        // Query for completed anime watchlist of the user
var query = `
query ($userName: String, $listStatus: MediaListStatus) { # Define which variables will be used in the query (id)
  MediaListCollection(userName: $userName, type: ANIME, status: $listStatus) {
    user {
      id
    }
    lists {
      name
      entries {
        media {
          id
        }
      }
    }
  }
}
`;

        // Define our query variables and values that will be used in the query request
        var variables = {
            userName: user,
            listStatus: status
        };

        // Define the config we'll need for our Api request
        var url = 'https://graphql.anilist.co',
            options = {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                },
                body: JSON.stringify({
                    query: query,
                    variables: variables
                })
            };

        // Make the HTTP Api request
        fetch(url, options).then(handleResponse)
            .then(handleData)
            .catch(handleError);
    }

    function handleResponse(response) {
        return response.json().then(function (json) {
            return response.ok ? json : Promise.reject(json);
        });
    }

    // Extracts anime id's from the data retrieved with the API request
    var animeIdList = [];
    function handleData(data) {
        var animeList = data.data.MediaListCollection.lists[0].entries;
        for (let i = 0; i<animeList.length; i++) {
            animeIdList.push(animeList[i].media.id);
        }
    }

    function handleError(error) {
        alert('Error, check console');
        console.error(error);
    }

    // Autoscrolls the page to the bottom until all data is loaded, then scrolls to the top
    var count = 0;
    var lastScrollHeight = 0;
    function loadPage(){
        var sh = document.documentElement.scrollHeight;
        if (sh != lastScrollHeight) {
            lastScrollHeight = sh;
            document.documentElement.scrollTop = sh;
            count = 0;
        } else {
            count++;
        }
        if (count === 5){
            clearInterval(loadingInterval);
            document.documentElement.scrollTop = 0;
            filterCharacters(animeIdList);
        }

    }

    // Filters the characters of a VA to only show characters in media present on the watchlist
    function filterCharacters(watchlist) {
        var characterList = document.querySelector(".character-roles .grid-wrap").childNodes;
        var characterArray = Array.from(characterList);
        var characterIdList = [];
        // For each character entry, checks if its media is in the watchlist. If it is not, the entry is removed.
        characterArray.forEach(function(item){
            var animeRe = /^https:\/\/anilist\.co\/anime\/(.*)\/.+/;
            var charRe = /^https:\/\/anilist\.co\/character\/(.*)\/.+/;
            var animeId = Number(item.querySelector(".media .content").href.match(animeRe)[1]);
            var charMatch = item.querySelector(".character .content").href.match(charRe);
            if (charMatch) {
                var charId = Number(charMatch[1]);
                if (animeIdList.indexOf(animeId) >= 0 && characterIdList.indexOf(charId) < 0){
                    characterIdList.push(charId);
                } else {
                    item.parentNode.removeChild(item);
                }
            } else {
                item.parentNode.removeChild(item);
            }
        });
    }

    // Creates buttons and displays it on the staff page
    var loadingInterval
    function addButtons(){
        // Create filter button
        /*
        var filterButton = document.createElement("BUTTON");
        filterButton.textContent = 'Filter';
        filterButton.setAttribute("style", "float:right");
        filterButton.addEventListener("click", function() {
            filterCharacters(animeIdList);
        }, false);
        */

        // Create loading button
        var loadingButton = document.createElement("BUTTON");
        loadingButton.textContent = 'Load page & filter';
        loadingButton.setAttribute("style", "float:right");
        loadingButton.addEventListener("click", function() {
            loadingInterval = window.setInterval(loadPage, 100);
            loadingButton.disabled = true;
        }, false);

        // Select character-roles header of staff page and add the buttons
        var charHeader = document.querySelector(".staff .character-roles h2");
        charHeader.appendChild(loadingButton);
        // charHeader.appendChild(filterButton);
    }

    // Script which filters the characters of a VA
    function filterScript(){
        addButtons();
        // TODO add sorting buttons
    }

    // Handles which script runs on which page
    function handleScripts(url){
        if (url.match(/^https:\/\/anilist\.co\/staff\/.+/)) {
            filterScript();
        };
    }

    $( window ).on( "load", function() {
        const user = getUser();
        getWatchlist(user, 'COMPLETED');
        getWatchlist(user, 'CURRENT');

        // Checks if the page url has changed and runs scripts accordingly
        // TODO change to event trigger if possible
        var current = "";
        var handle = 0;
        setInterval(function(){
            if(document.URL != current){
                clearTimeout(handle);
                current = document.URL;
                handle = setTimeout(function(){
                    handleScripts(current)
                }, 1000);
            };
        },200);
    });
})();