Greasy Fork is available in English.

MyAnimeList(MAL) - Voice Actor Filter

This script can filter/sort Voice Acting roles, Anime Staff positions and Published Manga

// ==UserScript==
// @name         MyAnimeList(MAL) - Voice Actor Filter
// @version      1.7.1
// @description  This script can filter/sort Voice Acting roles, Anime Staff positions and Published Manga
// @author       Cpt_mathix
// @match        https://myanimelist.net/people/*
// @exclude      https://myanimelist.net/people/*/*/*
// @match        https://myanimelist.net/people.php?id=*
// @match        https://myanimelist.net/character/*
// @exclude      https://myanimelist.net/character/*/*/*
// @match        https://myanimelist.net/character.php?id=*
// @license      GPL-2.0-or-later; http://www.gnu.org/licenses/gpl-2.0.txt
// @grant        GM_getValue
// @grant        GM_setValue
// @noframes
// @namespace    https://greasyfork.org/users/16080
// ==/UserScript==

var elementsVA,
	temp_deactivate_compressor = false,
    people = false,
    characters = false;

if (/http.*:\/\/myanimelist.net\/people\/\D*/.test(document.location.href)
   || /http.*:\/\/myanimelist.net\/people\.php\?id=\D*/.test(document.location.href)) {
    people = true;
    initPeople();
} else {
    characters = true;
    initCharacters();
}

// ==============================================
// ================= CHARACTERS =================
// ==============================================

function initCharacters() {
    $('div.normal_header, div.border_solid').each(function() {
        if ($(this).text().includes("Voice Actors")) {
            var anime = false;
            var languages = new Set();

            $(this).find('~ table').each(function() {
                if ($(this).has('table').length) {
                    anime = true;
                    $(this).find('table tr').each(function() {
                        prepareVAElement($(this), languages);
                    });
                } else if (!anime) {
                    prepareVAElement($(this), languages);
                }
            });

            addStyle($(this)[0]);

            createHrElement($(this)[0]);
            createLanguageCheckboxes(languages, $(this)[0]);
        }
    });
}

function prepareVAElement(element, languages) {
    var voiceActorLanguage = $(element).find('small').text().split(" ")[0].replace(/[^\w]/gi, '');

    $(element).addClass("VAElement");
    $(element).addClass(voiceActorLanguage);
    languages.add(voiceActorLanguage);
}

function createLanguageCheckboxes(languages, element) {
    for(var language of Array.from(languages).sort().reverse()) {
        var html = '<label>' + language + ': </label>' +
        '<input type="checkbox" class="checkbox" name="' + language + '" id="' + language + '" title="Show ' + language + ' Voice Actors" style="margin-right:10px;">';

        element.insertAdjacentHTML('afterend', html);

        var showLanguage = getSetting(language, true);
        $("#"+ language + "").prop("checked", showLanguage);
        startLanguageFilter(language, showLanguage);
    }
}

function startLanguageFilter(language, show) {
    $(".VAElement." + language).each(function(i) {
        $(this).toggle(show);
    });
}

// ==============================================
// =================== PEOPLE ===================
// ==============================================

function initPeople() {
	addCheckboxes();

    $(".navi-people-character > .btn-show-sort.js-btn-show-sort").hide();

	// preparing VA table for sorting and compressing
	$('table.js-table-people-character > tbody > tr:nth-child(n)').each(function(i) {
        var anime = encodeURIComponent($(this).find('.js-people-title')[0].textContent.trim());
		var char = encodeURIComponent($(this).find('td:nth-child(3) > div:nth-of-type(1)')[0].textContent.trim());
		var main = $(this).find('td:nth-child(3) > div:nth-of-type(2)')[0].textContent.trim();
        var popularity = $(this).find('.js-people-favorites')[0].textContent.split(" ")[0].trim();
		var recent = $(this).find('.entry-date')[0].textContent.trim();
		var reverse_recent = 99999999999999 - parseInt(recent);
		var reverse_popularity = 99999999999999 - parseInt(popularity);

        var sortData = '{"default":"' + anime + '","character":"' + char + '","popularity":"' + popularity + '","main":"' + main + anime + '","recent":"' + recent + '","main_char":"' + main + char + '","main_rec":"' + main + reverse_recent + '","main_pop":"' + main + reverse_popularity + '"}';
		$(this).attr("data-sort", sortData);
	});

	initElements(true);
	startFilter('default');
}

// backup elements
function initElements(init) {
	elementsVA = $('table.js-table-people-character > tbody > tr').not('.hidden-tr');
}

// core of the script, filtering
function startFilter(value) {
	switch (value) {
		case 'Sorter':
			if (temp_deactivate_compressor) {
				temp_deactivate_compressor = false;
				startFilter("Compressor");
			} else {
				sortVATable(getSetting('Sorter1'), getSetting('Sorter2'), getSetting('Sorter3'), getSetting('Sorter4'));
			}
			break;

		case 'Compressor':
            if (getSetting('Compressor')) {
                compressVATable(false);
                sortVATable(getSetting('Sorter1'), getSetting('Sorter2'), getSetting('Sorter3'), getSetting('Sorter4'));
                compressVATable(true);
            } else {
                compressVATable(false);
            }

            initElements(false);
            activateVAFilter(getSetting('VA'), getSetting('VA2'));
			reinitAddButtons();
			break;

        case 'VA':
            activateVAFilter(getSetting('VA'), getSetting('VA2'));
			break;

		default:
			sortVATable(getSetting('Sorter1'), getSetting('Sorter2'), getSetting('Sorter3'), getSetting('Sorter4'));
			compressVATable(getSetting('Compressor'));
			initElements(false);
			activateVAFilter(getSetting('VA'), getSetting('VA2'));
			reinitAddButtons();
			break;
	}
}

// Voice Actor roles filter
function activateVAFilter(conditionEdit, conditionAdd) {
    // var elements = $('table.VATable > tbody > tr').not('.hidden-tr');
	for (var i = 0; i < elementsVA.length; i++) {
		filterAddEdit(conditionEdit, conditionAdd, elementsVA[i]);
	}
}

function filterAddEdit(conditionEdit, conditionAdd, element) {
    $(element).find('a.Lightbox_AddEdit').each(function() {
        var type = $(this).attr('class');
        if (conditionEdit && type.indexOf('button_edit') > -1) {
            $(element).hide();
        } else if (conditionAdd && type.indexOf('button_add') > -1) {
            $(element).hide();
        } else {
            $(element).show();
            return false;
        }
    });
}

function compressVATable(condition) {
	var content = $('table.js-table-people-character > tbody');
	var listitems = content.children('tr').get();

	if (condition) { // compress
		var listid = [];
		for (var i = 0; i < listitems.length; i++) {
			listid.push(getCharacterId(listitems[i]));
		}
		for (var j = 0; j < listid.length; j++) {
			var hit = $.inArray(listid[j], listid);
			if (hit != j) {
				mergeVAElement(listitems[j], listitems[hit]);
			}
		}
	} else { // decompress
		for (var k = 0; k < listitems.length; k++) {
			var listitem = listitems[k];
			content = $(listitem).find('td:nth-child(2)')[0];
			if ($(listitem)[0].className.indexOf("hidden-tr") > -1) {
                $(content).find('.duplicateClass').removeClass("duplicateClass");
				$(listitem).removeClass("hidden-tr");
                $(listitem).show();
			} else if ($(content).find('.duplicateClass').length > 0) {
				$(content).find('.duplicateClass').remove();
				$(content).find(':hidden:not(.entry-date)').show();

                $(content).css("display", "");
                $(content).css("flex-flow", "");
                $(content).css("min-height", "");
                $(content).css("align-content", "");
                $(content).children().each(function() {
                    $(this).css("margin-right", "");
                });

				if ($(listitem).data("sort").main_orig === "Supporting") {
					$(listitem).find('td:nth-child(3) > div:nth-child(2)').text("Supporting");
					$(listitem).data("sort").main = "Supporting";
					$(listitem).data("sort").main_orig = "";
					$(listitem).data("sort").main_char = $(listitem).data("sort").main_char.replace("Main", "Supporting");
					$(listitem).data("sort").main_rec = $(listitem).data("sort").main_rec.replace("Main", "Supporting");
                    $(listitem).data("sort").main_pop = $(listitem).data("sort").main_pop.replace("Main", "Supporting");
				}
			}
		}
	}
}

function mergeVAElement(duplicate, element) {
    // Add duplicate class (easy removal later when decompressing)
    var duplicateContent = $(duplicate).find('td:nth-child(2)');
    $(duplicateContent).children().each(function() {
        $(this).addClass("duplicateClass");
    });

	var duplicateInfo = $(duplicateContent)[0].innerHTML;
	$(duplicate).addClass("hidden-tr");
	$(duplicate).hide();

    var content = $(element).find('td:nth-child(2)');

	// add info to element
	$(content)[0].innerHTML += '<div class="duplicateClass" style="width:100%"></div>' + duplicateInfo;

    // set info on one line
    $(content).css("display", "flex");
    $(content).css("flex-flow", "row wrap");
    $(content).css("min-height", "68px");
    $(content).css("align-content", "start");
    $(content).children().each(function() {
        $(this).css("margin-right", "8px");
    });

	// if character has one main role, change supporting to main
	if ($(element).data("sort").main.length > $(duplicate).data("sort").main.length) {
		$(element).find('td:nth-child(3) > div:nth-child(2)').text("Main");
		$(element).data("sort").main = "Main";
		$(element).data("sort").main_orig = "Supporting";
		$(element).data("sort").main_char = $(element).data("sort").main_char.replace("Supporting", "Main");
		$(element).data("sort").main_rec = $(element).data("sort").main_rec.replace("Supporting", "Main");
		$(element).data("sort").main_pop = $(element).data("sort").main_pop.replace("Supporting", "Main");
	}
}

// condition1 = sorter characters, condition2 = sorter main/supporting, condition 3 = most recent and condition 4 = popularity
function sortVATable(condition1, condition2, condition3, condition4) {
	var sortType;
	if (condition1 && condition2) {
		sortType = "main_char";
	} else if (condition2 && condition3) {
		sortType = "main_rec";
	} else if (condition4 && condition2) {
		sortType = "main_pop";
	} else if (condition1) {
		sortType = "char";
	} else if (condition2) {
		sortType = "main";
	} else if (condition3) {
		sortType = "recent";
	} else if (condition4) {
		sortType = "popularity";
	} else {
		sortType = "default";
	}

	var content = $('table.js-table-people-character > tbody');
	var listitems = content.children('tr').get();

	switch(sortType) {
		case 'main_char':
			listitems.sort(function(a, b) {
				var compA = $(a).data("sort").main_char;
				var compB = $(b).data("sort").main_char;
				if (compA == compB) {
					return (getAnimeId(a) < getAnimeId(b)) ? -1 : 1;
				}
				return (compA < compB) ? -1 : 1;
			});
			$.each(listitems, function(idx, itm) {
				$(content).append(itm);
			});
			break;

		case 'main_rec':
			listitems.sort(function(a, b) {
				var compA = $(a).data("sort").main_rec;
				var compB = $(b).data("sort").main_rec;
				if (compA == compB) {
					return (getAnimeId(a) < getAnimeId(b)) ? -1 : 1;
				}
				return (compA < compB) ? -1 : 1;
			});
			$.each(listitems, function(idx, itm) {
				$(content).append(itm);
			});
			break;

        case 'main_pop':
			listitems.sort(function(a, b) {
				var compA = $(a).data("sort").main_pop;
				var compB = $(b).data("sort").main_pop;
				if (compA == compB) {
					return (getAnimeId(a) < getAnimeId(b)) ? -1 : 1;
				}
				return (compA < compB) ? -1 : 1;
			});
			$.each(listitems, function(idx, itm) {
				$(content).append(itm);
			});
			break;

		case 'char':
			listitems.sort(function(a, b) {
				var compA = $(a).data("sort").character;
				var compB = $(b).data("sort").character;
				if (compA == compB) {
					return (getAnimeId(a) < getAnimeId(b)) ? -1 : 1;
				}
				return (compA < compB) ? -1 : 1;
			});
			$.each(listitems, function(idx, itm) {
				$(content).append(itm);
			});
			break;

		case 'main':
			listitems.sort(function(a, b) {
				var compA = $(a).data("sort").main;
				var compB = $(b).data("sort").main;
				if (compA == compB) {
					return (parseInt($(a).data("sort").default) < parseInt($(b).data("sort").default)) ? -1 : 1;
				}
				return (compA < compB) ? -1 : 1;
			});
			$.each(listitems, function(idx, itm) {
				$(content).append(itm);
			});
			break;

		case 'recent':
			listitems.sort(function(a, b) {
				var compA = $(a).data("sort").recent;
				var compB = $(b).data("sort").recent;
				if (compA == compB) {
					return (getAnimeId(a) < getAnimeId(b)) ? -1 : 1;
				}
				return (parseInt(compA) > parseInt(compB)) ? -1 : 1;
			});
			$.each(listitems, function(idx, itm) {
				$(content).append(itm);
			});
			break;

        case 'popularity':
			listitems.sort(function(a, b) {
				var compA = $(a).data("sort").popularity;
				var compB = $(b).data("sort").popularity;
				if (compA == compB) {
					return (getAnimeId(a) < getAnimeId(b)) ? -1 : 1;
				}
				return (parseInt(compA) > parseInt(compB)) ? -1 : 1;
			});
			$.each(listitems, function(idx, itm) {
				$(content).append(itm);
			});
			break;

		default:
			listitems.sort(function(a, b) {
				var compA = $(a).data("sort").default;
				var compB = $(b).data("sort").default;
				if (compA == compB) {
					return (getAnimeId(a) < getAnimeId(b)) ? -1 : 1;
				}
				return (compA < compB) ? -1 : 1;
			});
			$.each(listitems, function(idx, itm) {
				$(content).append(itm);
			});
			break;
	}
}

function addCheckboxes() {
	var elements = document.getElementsByClassName('normal_header');

	if (elements[0].textContent.indexOf("Voice Acting Roles") >= 0) {
		elements[0].className += " VAHeader";

        var navi = $('.js-navi-people-character')[0];
        createCompactViewCheckbox(navi, "afterbegin");
        createVrLine(navi, "afterbegin");
		createSortCheckboxes(navi, "afterbegin");

        if (isLoggedIn()) {
            createVrLine(navi, "afterbegin");
            createAddEdit("VA", navi, "afterbegin");
        }
	}

	addStyle(elements[0]);
}

function addStyle(element) {
	var css = '<style>input[type="checkbox"] {margin: -1px 0 0 0;vertical-align: middle;}' +
		' a.vafilter_add, a.vafilter_edit {border: solid #000;border-width: 0.1em;padding: 1px 4px 1px 4px;background-color: #f6f6f6;font-size: 9px;}' +
		' span.vrline {border-left: solid #000;border-width: 0.1em;margin-left: 0.5em;margin-right: 0.5em;}</style>';

	element.insertAdjacentHTML('beforebegin', css);
}

function createHrElement(element, placement = 'afterend') {
	var html = '<hr style="border: #d8d8d8 1px solid;border-bottom: 0;">';
	element.insertAdjacentHTML(placement, html);
}

function createVrLine(element, placement = 'afterend') {
    var html = '<span class="vrline">';
    element.insertAdjacentHTML(placement, html);
}

function createAddEdit(type, element, placement = 'afterend') {
	var html = '<span>Hide: </span>' +
        '<label>On MyList </label>' +
        '<input type="checkbox" class="checkbox" name="' + type + '" id="' + type + '" title="Hide entries on your list">' +
        '<span> - </span>' +
        '<label>Not on MyList </label>' +
        '<input type="checkbox" class="checkbox" name="' + type + '" id="' + type + '2" title="Hide entries not on your list">';

    element.insertAdjacentHTML(placement, html);

	$("#"+ type + "").prop("checked", getSetting(type));
	$("#"+ type + "2").prop("checked", getSetting(type + "2"));
}

function createSortCheckboxes(element, placement = 'afterend') {
	var html = '</span><span>Sort by: </span>' +
		'<label>Character </label>' +
		'<input type="checkbox" class="checkbox" name="Sorter1" id="Sorter1" title="Sort by Character name">' +
		'<span> - </span>' +
        '<label>Popularity </label>' +
		'<input type="checkbox" class="checkbox" name="Sorter4" id="Sorter4" title="Sort by Popularity">' +
		'<span> - </span>' +
		'<label>Main/Supporting </label>' +
		'<input type="checkbox" class="checkbox" name="Sorter2" id="Sorter2" title="Sort by Main/Supporting">' +
		'<span> - </span>' +
		'<label>Most Recent </label>' +
		'<input type="checkbox" class="checkbox" name="Sorter3" id="Sorter3" title="Sort by Added to DB">';

	element.insertAdjacentHTML(placement, html);

	$("#Sorter1").prop("checked", getSetting("Sorter1"));
	$("#Sorter2").prop("checked", getSetting("Sorter2"));
	$("#Sorter3").prop("checked", getSetting("Sorter3"));
	$("#Sorter4").prop("checked", getSetting("Sorter4"));
}

function createCompactViewCheckbox(element, placement = 'afterend') {
	var html = '</span><span>Compressed view: </span>' +
		'<input type="checkbox" class="checkbox" name="Compressor" id="Compressor" title="Activate compressed view">';

	element.insertAdjacentHTML(placement, html);

	$("#Compressor").prop("checked", getSetting("Compressor"));
}

function reinitAddButtons() {
	$('.Lightbox_AddEdit').fancybox({
		'width'			: 700,
		'height'		: '85%',
		'overlayShow'	: false,
		'titleShow'     : false,
		'type'          : 'iframe'
	});
}

function isLoggedIn() {
    return document.querySelector('.header-profile-link') !== null;
}

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

// Get a setting of type
function getSetting(type, notFoundValue) {
	var value = GM_getValue('MALVA_' + type);
	if (value !== undefined)
		return value;
	else
		return notFoundValue || false;
}

function getAnimeId(element) {
	return parseInt($(element).find('td:nth-child(2) a')[0].href.match(/\d+/g)[0]);
}

function getCharacterId(element) {
	return parseInt($(element).find('td:nth-child(3) a')[0].href.match(/\d+/g)[0]);
}

$("input:checkbox").on('click', function() {
	var $box = $(this);
	if ($box.is(":checked")) {
		var group = "input[name='" + $box.attr("name") + "']";
		$(group).each( function() {
			$(this).prop("checked", false);
			saveSetting($(this).attr('id'), false);
		});
		$box.prop("checked", true);
		saveSetting($box.attr('id'), true);
	} else {
		$box.prop("checked", false);
		saveSetting($box.attr('id'), false);
	}

    if ($box.attr("name").includes("Sorter") && !$box.attr("name").includes("Sorter2")) {
        temp_deactivate_compressor = getSetting("Compressor");

        $("#Sorter1").prop("checked", $box.attr("name").includes("Sorter1") && $box.is(":checked"));
        $("#Sorter3").prop("checked", $box.attr("name").includes("Sorter3") && $box.is(":checked"));
		$("#Sorter4").prop("checked", $box.attr("name").includes("Sorter4") && $box.is(":checked"));

        saveSetting("Sorter1", $box.attr("name").includes("Sorter1") && $box.is(":checked"));
		saveSetting("Sorter3", $box.attr("name").includes("Sorter3") && $box.is(":checked"));
		saveSetting("Sorter4", $box.attr("name").includes("Sorter4") && $box.is(":checked"));
    }

    if (people) {
        startFilter($box.attr('id').replace(/\d/g,''));
    } else if (characters) {
        startLanguageFilter($box.attr('id'), $box.is(":checked"));
    }
});