MyAnimeList(MAL) - Voice Actor Filter

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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"));
    }
});