// ==UserScript==
// @name MyAnimeList(MAL) - Voice Actor Filter
// @version 1.3.0
// @description This script can filter Voice Acting roles, Anime Staff positions and Published Manga
// @author Cpt_mathix
// @include *://myanimelist.net/people/*
// @include *://myanimelist.net/people.php?id=*
// @grant GM_getValue
// @grant GM_setValue
// @noframes
// @namespace https://greasyfork.org/users/16080
// ==/UserScript==
var elementsVA;
var elementsAS;
var elementsPM;
init();
function init() {
addCheckboxes();
// preparing VA & AS tables for sorting and compressing
$('table.VATable > tbody > tr:nth-child(n)').each(function(i) {
var char = $(this).find('td:nth-child(3) > a')[0].textContent;
var main = $(this).find('td:nth-child(3) > div')[0].textContent;
var id = getAnimeId($(this));
$(this).attr("data-sort", '{"default":"' + i + '","character":"' + char + '","main":"' + main + '","both":"' + main + char + '","recent":"' + id + '"}');
var spaceit = $(this).find('td:nth-child(2) > div');
$(spaceit).replaceWith(function() {
$(spaceit).find('a')[0].style.marginLeft = "5px";
return $('a', this);
});
});
$('table.ASTable > tbody > tr:nth-child(n)').each(function() {
var spaceit = $(this).find('.spaceit_pad');
var lightbox = $(spaceit).find('a');
lightbox[0].style.marginLeft = "5px";
$(lightbox).insertAfter($(this).find('td:nth-child(2)').find(':first-child')); //for some kind of reason, this lightbox is a clone
$(lightbox).remove(); // remove original
var spaceitText = $(spaceit).find('small');
spaceitText[0].innerHTML = spaceitText[0].innerHTML;
});
compressASTable();
initElements(true);
startFilter('default');
}
// backup elements
function initElements(init) {
if (init) {
elementsVA = $('table.VATable > tbody > tr').not('.hidden-tr');
elementsAS = $('table.ASTable > tbody > tr').not('.hidden-tr');
elementsPM = $('table.PMTable > tbody > tr').not('.hidden-tr');
} else { // refresh only VA because that's the only structure that might change
elementsVA = $('table.VATable > tbody > tr').not('.hidden-tr');
}
}
// core of the script, filtering
function startFilter(value) {
switch (value) {
case 'Sorter':
sortVATable(getSetting('Sorter'), getSetting('Sorter2'), getSetting('Sorter3'));
break;
case 'Sorter2':
sortVATable(getSetting('Sorter'), getSetting('Sorter2'), getSetting('Sorter3'));
break;
case 'Sorter3':
sortVATable(getSetting('Sorter'), getSetting('Sorter2'), getSetting('Sorter3'));
break;
case 'Compressor':
compressVATable(getSetting('Compressor'));
// initialize new backup (compress changes dom structure)
initElements(false);
// activate VAfilter with new backups
activateVAFilter(getSetting('VA'), getSetting('VA2'));
reinitAddButtons();
break;
case 'VA':
activateVAFilter(getSetting('VA'), getSetting('VA2'));
break;
case 'AS':
activateASFilter(getSetting('AS'), getSetting('AS2'));
break;
case 'PM':
activatePMFilter(getSetting('PM'), getSetting('PM2'));
break;
default:
sortVATable(getSetting('Sorter'), getSetting('Sorter2'), getSetting('Sorter3'));
compressVATable(getSetting('Compressor'));
initElements('false');
activateAllFilters();
reinitAddButtons();
break;
}
}
function activateAllFilters() {
activateVAFilter(getSetting('VA'), getSetting('VA2'));
activateASFilter(getSetting('AS'), getSetting('AS2'));
activatePMFilter(getSetting('PM'), getSetting('PM2'));
}
// Voice Actor roles filter
function activateVAFilter(conditionEdit, conditionAdd) {
for (var i = 0; i < elementsVA.length; i++) {
var tr = elementsVA[i];
$(elementsVA[i]).find('a.Lightbox_AddEdit').each(function() {
var content = $(this).parent()[0];
var type = $(this).attr('class');
if (content.children.length > 2) {
compressedVAFilter(tr, this, conditionEdit, conditionAdd, type); // when compressed = true and tr has multiple lightboxes (for different anime)
} else {
if (conditionEdit && type.indexOf('button_edit') > -1) {
$(tr).hide();
} else if (conditionAdd && type.indexOf('button_add') > -1) {
$(tr).hide();
} else {
$(tr).show();
}
}
});
}
}
function compressedVAFilter(tr, lightbox, conditionEdit, conditionAdd, type) {
if (conditionEdit && type.indexOf('button_edit') > -1) {
$(lightbox).hide();
$(lightbox).next().hide();
$(lightbox).prev().hide();
if (isHiddendiv(tr))
$(tr).hide();
} else if (conditionAdd && type.indexOf('button_add') > -1) {
$(lightbox).hide();
$(lightbox).next().hide();
$(lightbox).prev().hide();
if (isHiddendiv(tr))
$(tr).hide();
} else {
$(lightbox).show();
$(lightbox).next().show();
$(lightbox).prev().show();
$(tr).show();
}
}
// if div is completly hidden (content inside is all hidden) => return true
function isHiddendiv(div, type) {
var lightboxes = $(div).find('a.Lightbox_AddEdit').not(':hidden');
return lightboxes.length == 0;
}
// Anime Staff positions filter
function activateASFilter(conditionEdit, conditionAdd) {
for (var i = 0; i < elementsAS.length; i++) {
var tr = elementsAS[i];
$(elementsAS[i]).find('a.Lightbox_AddEdit').each(function() {
var type = $(this).attr('class');
if (conditionEdit && type.indexOf('button_edit') > -1) {
$(tr).hide();
} else if (conditionAdd && type.indexOf('button_add') > -1) {
$(tr).hide();
} else {
$(tr).show();
}
});
}
}
// Published Manga filter
function activatePMFilter(conditionEdit, conditionAdd) {
for (var i = 0; i < elementsPM.length; i++) {
var tr = elementsPM[i];
$(elementsPM[i]).find('a.Lightbox_AddEdit').each(function() {
var type = $(this).attr('class');
if (conditionEdit && type.indexOf('button_edit') > -1) {
$(tr).hide();
} else if (conditionAdd && type.indexOf('button_add') > -1) {
$(tr).hide();
} else {
$(tr).show();
}
});
}
}
function compressVATable(condition) {
var content = $('table.VATable > 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 i = 0; i < listitems.length; i++) {
content = $(listitems[i]).find('td:nth-child(2)')[0];
if ($(listitems[i])[0].className.indexOf("hidden-tr") > -1) {
$(listitems[i]).removeClass("hidden-tr");
}
if (content.children.length > 2) {
$(content).find(':gt(1)').remove();
$(content).find(':hidden').show();
}
}
}
}
function mergeVAElement(duplicate, element) {
var duplicateInfo = $(duplicate).find('td:nth-child(2)')[0].innerHTML;
$(duplicate).addClass("hidden-tr");
$(duplicate).hide();
// add info to element
$(element).find('td:nth-child(2)')[0].innerHTML += '<br>' + duplicateInfo;
}
function compressASTable() {
var content = $('table.ASTable > tbody');
var listitems = content.children('tr').get();
var listid = [];
for (var i = 0; i < listitems.length; i++) {
listid.push(getAnimeId(listitems[i]));
}
for (var j = 0; j < listid.length; j++) {
var hit = $.inArray(listid[j], listid);
if (hit != j) {
mergeASElement(listitems[j], listitems[hit]);
}
}
}
function mergeASElement(duplicate, element) {
var duplicateInfo = $(duplicate).find('.spaceit_pad')[0].outerHTML;
$(duplicate).addClass("hidden-tr");
$(duplicate).hide();
// add info to element
$(element).find('td:nth-child(2)')[0].innerHTML += duplicateInfo;
}
// condition1 = sorter characters and condition2 = sorter main/supporting
function sortVATable(condition1, condition2, condition3) {
var sortType;
if (condition1 && condition2) {
sortType = "both";
} else if (condition1 && !condition2) {
sortType = "char";
} else if (!condition1 && condition2) {
sortType = "main";
} else if (condition3) {
sortType = "recent";
} else {
sortType = "default";
}
var content = $('table.VATable > tbody');
var listitems = content.children('tr').get();
switch(sortType) {
case 'both':
listitems.sort(function(a, b) {
var compA = $(a).data("sort").both;
var compB = $(b).data("sort").both;
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;
default:
listitems.sort(function(a, b) {
var compA = parseInt($(a).data("sort").default);
var compB = parseInt($(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].nextSibling.tagName == "TABLE") {
elements[0].className += " VAHeader";
elements[0].nextSibling.className = "VATable";
}
if (elements[1].nextSibling.tagName == "TABLE") {
elements[1].className += " ASHeader";
elements[1].nextSibling.className = "ASTable";
}
if (elements[2].nextSibling.tagName == "TABLE") {
elements[2].className += " PMHeader";
elements[2].nextSibling.className = "PMTable";
}
createHrElement(elements[0]);
createHrElement(elements[1]);
createHrElement(elements[2]);
createCompactViewCheckbox(elements[0]);
createSortCheckboxes(elements[0]);
createAddEdit("VA", elements[0]);
createAddEdit("AS", elements[1]);
createAddEdit("PM", elements[2]);
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) {
var html = '<hr>';
element.insertAdjacentHTML('afterend', html);
}
function createAddEdit(type, element) {
var html = '<span>Hide: </span>' +
'<label>Add </label>' +
'<input type="checkbox" class="checkbox" name="' + type + '" id="' + type + '2" title="Hide entries not on your list">' +
'<span> - </span>' +
'<label>Edit </label>' +
'<input type="checkbox" class="checkbox" name="' + type + '" id="' + type + '" title="Hide entries on your list">';
element.insertAdjacentHTML('afterend', html);
$("#"+ type + "").prop("checked", getSetting(type));
$("#"+ type + "2").prop("checked", getSetting(type + "2"));
}
function createSortCheckboxes(element) {
var html = '<span class="vrline"></span><span>Sort by: </span>' +
'<label>Character </label>' +
'<input type="checkbox" class="checkbox" name="Sorter" id="Sorter" title="Sort by Character name">' +
'<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('afterend', html);
$("#Sorter").prop("checked", getSetting("Sorter"));
$("#Sorter2").prop("checked", getSetting("Sorter2"));
$("#Sorter3").prop("checked", getSetting("Sorter3"));
}
function createCompactViewCheckbox(element) {
var html = '<span class="vrline"></span><span>Compressed view: </span>' +
'<input type="checkbox" class="checkbox" name="Compressor" id="Compressor" title="Activate compressed view">';
element.insertAdjacentHTML('afterend', html);
$("#Compressor").prop("checked", getSetting("Compressor"));
}
function reinitAddButtons() {
var pageEdit = document.querySelector('#addtolist > table > tbody > tr:nth-child(4) > td:nth-child(2) > small > a');
if (pageEdit) {
pageEdit.className = "Lightbox_AddEdit button_add";
pageEdit.href += "&hideLayout";
}
var pageAddDetails = document.querySelector('#addtolistresult > a');
if (pageAddDetails) {
pageAddDetails.className = "Lightbox_AddEdit button_add";
pageAddDetails.href += "&hideLayout";
}
$('.Lightbox_AddEdit').fancybox({
'width' : 700,
'height' : '85%',
'overlayShow' : false,
'titleShow' : false,
'type' : 'iframe'
});
}
// 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) {
var value = GM_getValue('MALVA_' + type);
if (value)
return value;
else
return false;
}
function getAnimeId(element) {
return parseInt($(element).find('td:nth-child(2) > a:nth-child(1)')[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:checkbox[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") === "Sorter") {
$("#Sorter3").prop("checked", false);
saveSetting("Sorter3", false);
}
if ($box.attr("name") === "Sorter2") {
$("#Sorter3").prop("checked", false);
saveSetting("Sorter3", false);
}
if ($box.attr("name") === "Sorter3") {
$("#Sorter").prop("checked", false);
saveSetting("Sorter", false);
$("#Sorter2").prop("checked", false);
saveSetting("Sorter2", false);
}
startFilter($box.attr('id').replace(/\d/g,''));
});