您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Allows to find missing relations and entries with wrong chapter/episode count.
当前为
// ==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. // @icon http://i.imgur.com/b7Fw8oH.png // @version 3.3 // @author akarin // @include /^http\:\/\/myanimelist\.net\/editlist\.php\?type=anime$/ // @include /^http\:\/\/myanimelist\.net\/panel\.php\?go=editmanga$/ // @grant none // @noframes // ==/UserScript== (function($) { var mal = { nickname: '', version: '', cache: '3.1', type: document.URL.match(/\?type=anime$/) ? 'anime' : 'manga', ajax: { delay: 300, timeout: 5000, url: 'http://myanimelist.net/' }, content: { body: $(), list: $(), done: $(), fail: $() }, entries: { done: 0, fail: 0, total: 0, left: {}, right: [], ignore: {} } }; mal.main = function(version) { if ($('#malLogin').length > 0) { return; } $.ajaxSetup({ timeout: mal.ajax.timeout }); mal.nickname = $('ul#nav li:first > ul > li > a:contains(Profile)').prop('href').match(/(?!.*\/).*$/)[0]; mal.version = version; if (mal.loadValue('mal.cache', '') !== mal.cache) { mal.entries.save(); // save empty data: clear } mal.content.body = $('<div id="mr_body"></div>').html('<h2 id="mr_body_title">Missing Relations <span><a href="http://greasyfork.org/scripts/9261" target="_blank">v' + mal.version + '</a></span></h2>'); mal.content.list = $('<div id="mr_list"></div>').appendTo(mal.content.body); mal.content.done = $('<span id="mr_status_done" style="color: green;"></span>'); mal.content.fail = $('<span id="mr_status_fail" style="color: #c32;"></span>'); mal.entries.load(); mal.content.update(false); $('<div></div>').hide().append(mal.content.body).insertAfter('#content'); $('<span id="mr_link"></span>') .append('<span> | </span>') .append($('<a href="#mr_body">Missing Relations</a>').fancybox({ 'hideOnContentClick': false, 'hideOnOverlayClick': true, 'transitionIn': 'none', 'transitionOut': 'none', 'titleShow': false, '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?')) { mal.entries.update(); } }) )) .append(') ').append(mal.content.done) .append(' ').append(mal.content.fail) .appendTo('#content > div:first'); }; mal.entries.updating = function() { return (mal.entries.done + mal.entries.fail) !== mal.entries.total ? true : false; }; mal.entries.load = function() { mal.entries.clear(); mal.entries.left = mal.loadValue('mal.entries.left', mal.entries.left); mal.entries.right = mal.loadValue('mal.entries.right', mal.entries.right); mal.entries.ignore = mal.loadValue('mal.entries.ignore', mal.entries.ignore); }; mal.entries.save = function() { mal.saveValue('mal.cache', mal.cache); mal.saveValue('mal.entries.left', mal.entries.left); mal.saveValue('mal.entries.right', mal.entries.right); mal.saveValue('mal.entries.ignore', mal.entries.ignore); }; mal.entries.clear = function() { mal.entries.done = 0; mal.entries.fail = 0; mal.entries.total = 0; mal.entries.left = {}; mal.entries.right = []; //mal.entries.ignore = {}; }; mal.entries.update = function() { if (mal.entries.updating()) { return; } var links = $('#content strong + a'); mal.entries.clear(); mal.entries.total = links.length; mal.content.done.text(''); mal.content.fail.text(''); $('#content span[class^="mr_status_id_"]').empty(); if (links.length === 0) { mal.content.update(true); return; } links.each(function(index) { var id = $(this).prop('href').match(/\d+$/)[0]; var span = $(this).parent().find('span[class="mr_status_id_' + id + '"]'); if (span.length === 0) { span = $('<span class="mr_status_id_' + id + '"></span>').appendTo($(this).parent()); } mal.entries.left[id] = true; setTimeout(function() { $.ajax(mal.ajax.url + mal.type + '/' + id) .done(function(data) { var title = data.match(/<div id="contentWrapper">[\s\S]*?<h1><div [\s\S]*?<\/div>(.+?)</)[1].trim(); mal.setRelations(id, title, data); if (!mal.checkCorrectInfo(id, data)) { mal.entries.right.push({ lId: id, lTitle: title, rId: '-1', rTitle: '' }); } span.html('<small style="color: green;">done</small>'); mal.content.done.text('done: ' + (++mal.entries.done) + '/' + mal.entries.total); mal.content.update(true); }) .fail(function(data) { span.html('<small style="color: red;">fail</small>'); mal.content.fail.text('fail: ' + (++mal.entries.fail)); mal.content.update(true); }); }, mal.ajax.delay * index); }); }; mal.setRelations = function(id, title, data) { var cnRe = new RegExp('<h2>Related (Anime|Manga)</h2>[\\s\\S]*?<h2>'); var idRe = new RegExp('/' + mal.type + '/(\\d+)/'); $('a', '<context>' + data.match(cnRe) + '</context>').each(function() { var idData = $(this).prop('href').match(idRe); if (idData !== null && idData.length > 1) { mal.entries.right.push({ lId: id, lTitle: title, rId: idData[1], rTitle: $(this).text().trim() }); } }); }; mal.checkCorrectInfo = function(id, data) { var myStatusData = data.match(/selected>(.*?)<\/option/); if (myStatusData === null) { return false; } var status = data.match(/>Status:<\/span>([\s\S]*?)<\/div>/)[1].trim(); var myStatus = myStatusData[1].trim(); if (mal.type === 'anime') { if ((status === 'Not yet aired' && myStatus !== 'Plan to Watch') || (status === 'Currently Airing' && myStatus === 'Completed')) { return false; } if (myStatus !== 'Completed') { return true; } var eps = data.match(/>Episodes:<\/span>([\s\S]*?)<\/div>/)[1].trim().replace('Unknown', '0'); var myEps = data.match(/name="myinfo_watchedeps"[\s\S]*?value="(\d*)"/)[1].trim(); if (parseInt(myEps) > parseInt(eps) || (myStatus === 'Completed' && parseInt(myEps) !== parseInt(eps))) { return false; } } else { if ((status === 'Not yet published' && myStatus !== 'Plan to Read') || (status === 'Publishing' && myStatus === 'Completed')) { return false; } if (myStatus !== 'Completed') { return true; } var vols = data.match(/>Volumes:<\/span>([\s\S]*?)<\/div>/)[1].trim().replace('Unknown', '0'); var myVols = data.match(/id="myinfo_volumes"[\s\S]*?value="(\d*)"/)[1].trim(); if (parseInt(myVols) > parseInt(vols) || (myStatus === 'Completed' && parseInt(myVols) !== parseInt(vols))) { return false; } var chap = data.match(/>Chapters:<\/span>([\s\S]*?)<\/div>/)[1].trim().replace('Unknown', '0'); var myChap = data.match(/id="myinfo_chapters"[\s\S]*?value="(\d*)"/)[1].trim(); if (parseInt(myChap) > parseInt(chap) || (myStatus === 'Completed' && parseInt(myChap) !== parseInt(chap))) { return false; } } return true; }; mal.content.update = function(save) { if (mal.entries.updating()) { return; } if (save === true) { mal.entries.save(); } if (mal.entries.right.length === 0) { mal.content.list.html('<p>No missing relations found.</p>'); return; } var table = $('<table id="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><tr><td class="mr_table_header">You\'ve watched this…</td><td class="mr_table_header">…so you might want to check this:</td></tr></table>'); var undel = $('<div id="mr_undelete"></div>').html('<p id="mr_undelete_msg" style="display: none;">There are <span id="mr_undelete_num" style="font-weight: bold;"></span> hidden relations. <a href="javascript:void(0);" title="Show ignored relations" onclick="window.mal.showIgnoredRelations();">Show them</a></p>'); var right = mal.entries.right.slice(0).sort(function(a, b) { return parseInt(a.rId) > parseInt(b.rId); }); for (var i = 0, prevId = ''; i < right.length; ++i) { if (parseInt(right[i].rId) < 0) { continue; } if (right[i].rId === prevId || mal.entries.left.hasOwnProperty(right[i].rId)) { right.splice(i--, 1); } else { prevId = right[i].rId; } } right.sort(function(a, b) { var comp = a.lTitle.toLowerCase().localeCompare(b.lTitle.toLowerCase()); if (parseInt(a.rId) < 0 && parseInt(b.rId) < 0) { return comp; } return parseInt(a.rId) < 0 ? false : (comp !== 0 ? comp : a.rTitle.toLowerCase().localeCompare(b.rTitle.toLowerCase())); }); var ulLeft = $('<ul></ul>'), ulRight = $('<ul></ul>'); $.each(right, function(index, rel) { // red relation if (parseInt(rel.rId) < 0) { // add left $('<li></li>').prepend(mal.getEntryLink(rel.lId, rel.lTitle)).appendTo(ulLeft); // add right if (ulRight.children().length === 0) { $('<li></li>') .prop('id', 'mr_li_red') .prepend(mal.getEntryWarning()) .appendTo(ulRight); } } // normal relation else { // add left if (ulLeft.children().length === 0) { $('<li></li>') .prepend(mal.getEntryLink(rel.lId, rel.lTitle)) .prepend(mal.getHideButton('window.hideRow(\'' + rel.lId + '\');', 'Hide all relations')) .appendTo(ulLeft); } // add right $('<li></li>') .prop('id', 'mr_li_' + rel.rId) .prepend(mal.getEntryLink(rel.rId, rel.rTitle)) .prepend(mal.getHideButton('window.hideRelation(\'' + rel.rId + '\');', 'Hide this relation')) .appendTo(ulRight); } // if this is a last relation if (index + 1 === right.length || // or this is a last relation in a group (parseInt(right[index + 1].rId) > -1 && (rel.rId < 0 || rel.lId !== right[index + 1].lId))) { $('<tr id="' + (rel.rId > 0 ? ('mr_tr_' + rel.lId) : 'mr_tr_red') + '"></tr>') .append($('<td class="mr_subject"></td>').append(ulLeft)) .append($('<td class="mr_proposed"></td>').append(ulRight)) .appendTo(table); ulLeft = $('<ul></ul>'); ulRight = $('<ul></ul>'); } }); mal.updateIgnoredRelations(right); mal.content.list.empty().append(undel).append(table); mal.hideIgnoredRelations(); }; mal.getEntryLink = function(id, title) { return $('<a title="' + title + '"href="' + mal.ajax.url + mal.type + '/' + id + '" target="_blank">' + title + '</a>'); }; mal.getEntryWarning = function() { return $('<div class="mr_warning">Wrong status or ' + (mal.type === 'anime' ? 'episode' : 'vol./chap.') + ' count</div>'); }; mal.getHideButton = function(func, title) { return $('<div class="mr_hide"><small><a href="javascript:void(0);" title="' + title + '" onclick="' + func + '">x</a></small></div>'); }; mal.hideRow = function(id, save) { var tr = $('tr[id="mr_tr_' + id + '"]', mal.content.list); if (tr.length === 0) { return; } $('td.mr_proposed li', tr).hide().each(function() { var rId = $(this).prop('id').match(/\d+/)[0]; mal.entries.ignore[rId] = true; }); tr.hide(); if (save === true) { mal.saveValue('mal.entries.ignore', mal.entries.ignore); var count = Object.keys(mal.entries.ignore).length; $('#mr_undelete_num', mal.content.list).text(count); $('#mr_undelete_msg', mal.content.list).toggle(count > 0); } }; mal.hideRelation = function(id, save) { var li = $('li[id="mr_li_' + id + '"]', mal.content.list); if (li.length === 0) { return; } var tr = li.hide().closest('tr'); var count = $('td.mr_proposed li:not([style*="display: none;"])', tr).length; tr.toggle(count > 0); mal.entries.ignore[id] = true; if (save === true) { mal.saveValue('mal.entries.ignore', mal.entries.ignore); count = Object.keys(mal.entries.ignore).length; $('#mr_undelete_num', mal.content.list).text(count); $('#mr_undelete_msg', mal.content.list).toggle(count > 0); } }; mal.hideIgnoredRelations = function() { $.each(mal.entries.ignore, function(id) { mal.hideRelation(id, false); }); var count = Object.keys(mal.entries.ignore).length; $('#mr_undelete_num', mal.content.list).text(count); $('#mr_undelete_msg', mal.content.list).toggle(count > 0); }; mal.showIgnoredRelations = function() { $('#mr_undelete_msg', mal.content.list).hide(); $('tr[id^="mr_tr_"]', mal.content.list).show(); $('li[id^="mr_li_"]', mal.content.list).show(); mal.saveValue('mal.entries.ignore', (mal.entries.ignore = {})); }; mal.updateIgnoredRelations = function(right) { var seen = {}; $.each(right, function(i, rel) { seen[rel.rId] = true; }); $.each(mal.entries.ignore, function(id) { if (seen.hasOwnProperty(id) === false) { delete mal.entries.ignore[id]; } }); mal.saveValue('mal.entries.ignore', mal.entries.ignore); }; mal.loadValue = function(key, def) { try { return JSON.parse(localStorage.getItem(mal.nickname + '#' + mal.type + '#' + key)); } catch (e) { return def; } }; mal.saveValue = function(key, val) { localStorage.setItem(mal.nickname + '#' + mal.type + '#' + key, JSON.stringify(val)); }; window.hideRow = function(id) { mal.hideRow(id, true); }; window.hideRelation = function(id) { mal.hideRelation(id, true); }; window.showIgnoredRelations = function() { mal.showIgnoredRelations(); }; $('<style type="text/css" />').html([ '#content span[class^="mr_status_id_"] { float: right; padding: 0 7px; }', 'div#mr_body { text-align: center; width: 650px; height: auto; }', 'div#mr_body a, div#mr_body a:visited { color: #1969cb; text-decoration: none; }', 'div#mr_body a:hover { color: #2d7de0; text-decoration: underline; }', 'div#mr_body #mr_body_title { font-size: 1.5em; font-weight: normal; text-align: center; margin: 0 0 1em 0; position: relative; border: 0; }', 'div#mr_body #mr_body_title span { font-size: 0.8em; font-weight: normal; }', 'div#mr_body #mr_body_title:after { content: ""; position: absolute; bottom: -14px; left: 0; width: 100%; height: 8px; border-top: 1px solid #eee; background: center bottom no-repeat radial-gradient(#f6f6f6, #fff 70%); background-size: 100% 16px; }', 'div#mr_body div#mr_undelete { background-color: #fff; padding: 0; margin: 0; }', 'div#mr_body p#mr_undelete_msg { margin: 0 0 10px; font-weight: normal; text-align: center; line-height: 1.6em; font-size: 1.1em; }', 'div#mr_list { width: auto; height: 680px; overflow-y: auto; margin: 18px auto 0; }', 'div#mr_list #relTable tr td { background-color: #fff; padding: 0.5em 0 0.5em 6px; font-weight: normal; text-align: left; box-shadow: 0px 1em 1em -1em #ddd inset; vertical-align: top; line-height: 1.6em; font-size: 1.1em; }', 'div#mr_list #relTable tr:hover td { background-color: #f5f5f5; }', 'div#mr_list #relTable tr td.mr_table_header { background-color: #f5f5f5; box-shadow: none; font-weight: bold; }', 'div#mr_list #relTable td div.mr_warning { width: 285px; color: #e43; font-weight: bold; }', 'div#mr_list #relTable td div.mr_hide { width: 15px; float: right; text-align: left; color: #c32; padding-left: 5px; }', 'div#mr_list #relTable td ul { list-style-type: none; margin: 0; padding: 0; }', 'div#mr_list #relTable td ul li { width: 100%; padding: 0; margin: 0; }', 'div#mr_list #relTable td ul li > a { display: block; width: 285px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }' ].join('')).appendTo('head'); mal.main(GM_info.script.version); })(jQuery);