// ==UserScript==
// @name MyAnimeList (MAL) Track Missing Relations
// @namespace https://greasyfork.org/users/7517
// @description Allows to find missing relations and entries with wrong chapter/episode count.
// @version 1.0.1
// @author akarin
// @include /^http\:\/\/myanimelist\.net\/editlist\.php\?type=anime$/
// @include /^http\:\/\/myanimelist\.net\/panel\.php\?go=editmanga$/
// @grant none
// @noframes
// ==/UserScript==
;(function() {
if ($('#malLogin').length > 0) {
return
}
const GET_DELAY = 300
const ANIME_T = 'anime'
const MANGA_T = 'manga'
const LIST_TYPE = document.URL.match(/\?type=anime$/) ? ANIME_T : MANGA_T
const NICKNAME = $('ul#nav li:first > ul > li > a:contains(Profile)').prop('href').match(/(?!.*\/).*$/)[0]
versionCheck(GM_info.script.version)
var total
var calc = -1
var left = []
var right = []
var $relBody = $('<div id="mr_body"></div>')
.css({
'text-align': 'center',
'width': '650px',
'height': 'auto'
})
.append('<div style="font-size: 1.1em; font-weight: bold; color: #000;">Missing Titles</div>')
.append($(' <small></small>').append(
$('<a href="javascript:void(0);">refresh</a>').click(function() {
$.fancybox.close()
if (true === confirm('Are you sure you want to recalculate missing relations?')) {
recalculate($('#content'))
}
})
))
.append('<br/>')
.append('<hr size="1" color="#ccc" width="98%" noshade>')
var $relList = $('<div id="mr_list"></div>').appendTo($relBody)
var content = loadValue('relList', null)
if (content !== null) {
$relList.html(content)
}
else {
$relList.html('<br><small>No missing relations found.</small>')
}
restyle($relList)
$('<div style="display: none;"></div>').append($relBody).insertAfter('#content')
$('<span id="mr_link"></span>')
.append('<span> | </span>')
.append($('<a href="#mr_body">Missing Relations</a>').fancybox({
'hideOnContentClick': false,
'hideOnOverlayClick': true,
'titleShow': false,
'transitionIn': 'none',
'transitionOut': 'none',
'scrolling': 'no'
}))
.append(' (')
.append($('<small></small>').append(
$('<a href="javascript:void(0);">refresh</a>').click(function() {
if (true === confirm('Are you sure you want to recalculate missing relations?')) {
recalculate($('#content'))
}
})
))
.append(') ')
.append('<span id="mr_status" style="color: green;"></span>')
.appendTo('#content > div:first')
function versionCheck(ver) {
var v = loadValue('version', null)
if (v === null || v !== ver) {
localStorage.clear()
saveValue('version', ver)
}
}
function recalculate(context) {
if (calc > -1) {
return
}
var $links = $('strong + a', context)
total = $links.length
if (total === 0) {
$relList.html('<br><small>No missing relations found.</small>')
return
}
calc = total
$('span#mr_status').text(total - calc + '/' + total)
$('span[id^="mr_id_"]').empty()
left = []
right = []
$relList.html('<br><small>Calculating missing relations...</small>')
$links.each(function(index) {
var id = $(this).prop('href').match(/\d+$/)[0]
var $span = $(this).parent().find('span[id="mr_' + id + '"]')
if ($span.length === 0) {
$('<span id="mr_id_' + id + '" style="float: right; margin: 0 7px;"></span>').appendTo($(this).parent())
}
left.push(parseInt(id))
setTimeout(function() {
$.get('/' + LIST_TYPE + '/' + id, function(data) {
checkRelations(id, data)
checkCorrectInfo(id, data)
$('span[id="mr_id_' + id + '"]').html('<small style="color: green;">done</small>')
$('span#mr_status').text(total - (--calc) + '/' + total)
finalize()
})
}, GET_DELAY * index)
})
}
function checkRelations(id, data) {
var title = data.match(/<div id="contentWrapper">[\s\S]*?<h1><div [\s\S]*?<\/div>(.+?)</)[1].trim()
var cnEx = LIST_TYPE === ANIME_T ? /<h2>Related Anime<\/h2>[\s\S]*?<h2>/ : /<h2>Related Manga<\/h2>[\s\S]*?<h2>/
var idEx = LIST_TYPE === ANIME_T ? /\/anime\/(\d+)\// : /\/manga\/(\d+)\//
$('a', '<context>' + data.match(cnEx) + '</context>').each(function() {
var idData = $(this).prop('href').match(idEx)
if (idData !== null && idData.length > 1) {
right.push({
lId: parseInt(id),
lTitle: title,
rId: parseInt(idData[1]),
rTitle: $(this).text().trim()
})
}
})
}
function checkCorrectInfo(id, data) {
var correct = true
do {
var title = data.match(/<div id="contentWrapper">[\s\S]*?<h1><div [\s\S]*?<\/div>(.+?)</)[1].trim()
var status = data.match(/>Status:<\/span>([\s\S]*?)<\/div>/)[1].trim()
var myStatus = data.match(/selected>(.*?)<\/option/)[1].trim()
if (status === (LIST_TYPE === ANIME_T ? 'Not yet aired' : 'Not yet published')) {
if (myStatus !== (LIST_TYPE === ANIME_T ? 'Plan to Watch' : 'Plan to Read')) {
correct = false
break
}
}
else if (status === (LIST_TYPE === ANIME_T ? 'Currently Airing' : 'Publishing')) {
if (myStatus === 'Completed') {
correct = false
break
}
}
if (LIST_TYPE === ANIME_T) {
var eps = data.match(/>Episodes:<\/span>([\s\S]*?)<\/div>/)[1].trim()
var myEps = data.match(/name="myinfo_watchedeps"[\s\S]*?value="(\d*)"/)[1].trim()
if (eps !== 'Unknown') {
if (parseInt(myEps) > parseInt(eps) || (myStatus === 'Completed' && parseInt(myEps) !== parseInt(eps))) {
correct = false
break
}
}
}
else {
var vols = data.match(/>Volumes:<\/span>([\s\S]*?)<\/div>/)[1].trim()
var myVols = data.match(/id="myinfo_volumes"[\s\S]*?value="(\d*)"/)[1].trim()
if (vols !== 'Unknown') {
if (parseInt(myVols) > parseInt(vols) || (myStatus === 'Completed' && parseInt(myVols) !== parseInt(vols))) {
correct = false
break
}
}
var chap = data.match(/>Chapters:<\/span>([\s\S]*?)<\/div>/)[1].trim()
var myChap = data.match(/id="myinfo_chapters"[\s\S]*?value="(\d*)"/)[1].trim()
if (chap !== 'Unknown') {
if (parseInt(myChap) > parseInt(chap) || (myStatus === 'Completed' && parseInt(myChap) !== parseInt(chap))) {
correct = false
break
}
}
}
} while (false);
if (correct === false) {
right.push({
lId: parseInt(id),
lTitle: title,
rId: -1,
rTitle: ''
})
}
}
function finalize() {
if (calc > 0) {
return
}
if (right.length === 0) {
$relList.html('<br><small>No missing relations found.</small>')
return
}
var $table = $('<table id="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"></table>')
.append($('<tr></tr>').html(
'<th>You\'ve watched this…</th>' +
'<th>…so you might want to check this:</th>'
))
right.sort(function(a, b) {
return a.rId > b.rId
})
var prev = -1
for (var i = 0; i < right.length; ++i) {
if (right[i].rId < 0) {
continue
}
if (right[i].rId === prev || left.indexOf(right[i].rId) > -1) {
right.splice(i--, 1)
}
else {
prev = right[i].rId
}
}
right.sort(function(a, b) {
if (a.lTitle === b.lTitle) {
return b.rId < 0 || a.rTitle > b.rTitle
}
return a.lTitle > b.lTitle
})
prev = -1
var count = 0
$.each(right, function(index, rel) {
var leftLink = ''
var rightLink = ''
var className = ''
if (rel.lId !== prev) {
leftLink = '<a href="/' + LIST_TYPE + '/' + rel.lId + '" target="_blank">' + rel.lTitle + '</a>'
className = ' class="first_row"'
if (rel.rId < 0) {
rightLink = '<div class="mr_warning">Wrong status or ' + (LIST_TYPE === ANIME_T ? 'episode' : 'chapter') + ' count</div>'
}
}
if (rel.rId > -1) {
rightLink = '<div class="mr_count"><small>' + (++count) + '</small></div>' +
'<a href="/' + LIST_TYPE + '/' + rel.rId + '" target="_blank">' + rel.rTitle + '</a>'
}
$table.append($('<tr></tr>').html(
'<td' + className + '>' + leftLink + '</td>' +
'<td' + className + '>' + rightLink + '</td>'
))
prev = rel.lId
})
$relList.empty().append($table)
saveValue('relList', $relList.html())
restyle($relList)
calc = -1
}
function restyle(context) {
$(context).css({
'width': '98%',
'height': '700px',
'overflow-y': 'auto',
'margin': '0 auto 15px'
})
$('#relTable th', context).css({
'width': '50%',
'background-color': '#f5f5f5',
'padding': '0.5em 1em',
'font-weight': 'bold',
'text-align': 'left'
})
$('#relTable td', context).css({
'width': '50%',
'padding': '0.2em 1em',
'font-weight': 'normal',
'text-align': 'left',
'border-top': '1px solid #eee'
})
$('#relTable td.first_row', context).css({
'border-top': '2px solid #bebebe'
})
$('#relTable tr:last-of-type td', context).css({
'border-bottom': '1px solid #eee'
})
$('#relTable td:last-of-type > div.mr_warning', context).css({
'color': '#E43',
'font-weight': 'bold'
})
$('#relTable td:last-of-type > div.mr_count', context).css({
'width': '25px',
'float': 'right',
'color': '#666',
'text-align': 'center'
})
}
function loadValue(name, defaultValue) {
var value = localStorage.getItem(NICKNAME + '#' + LIST_TYPE + '#' + name)
return value ? value : defaultValue
}
function saveValue(name, value) {
localStorage.setItem(NICKNAME + '#' + LIST_TYPE + '#' + name, value)
}
})()