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