您需要先安装一个扩展,例如 篡改猴、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 4.2 // @author akarin // @include /^http:\/\/myanimelist\.net\/panel\.php(\?go=editmanga)?$/ // @include /^http:\/\/myanimelist\.net\/editlist\.php\?type=anime$/ // @grant none // @noframes // ==/UserScript== (function($) { var mal = { cache: '4.0', ajax: { delay: 300, timeout: 5000 }, name: $('#header-menu span.profile-name').text().trim(), type: document.URL.match(/\?type=anime$/) ? 'anime' : 'manga' }; function main() { if (mal.name.length === 0) { return; } mal.content.setVersion('v' + GM_info.script.version); mal.content.body.append(mal.content.list); $('<div></div>').hide().append(mal.content.body).insertAfter('#contentWrapper'); if (document.URL.match(/^http:\/\/myanimelist\.net\/panel\.php$/)) { pagePanel(); } else { pageList(); } } function pageList() { $.ajaxSetup({ timeout: mal.ajax.timeout }); if (!mal.entries.checkVersion()) { mal.entries.clear(); mal.entries.save(); } mal.entries.load(); mal.content.update(false); $('<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($('<span></span>') .append('scan: ') .append($('<a href="javascript:void(0);">all</a>').click(function() { if (true === confirm('Recalculate all entries?')) { mal.entries.update(false); } })) .append(', ') .append($('<a href="javascript:void(0);">new</a>').click(function() { if (true === confirm('Recalculate new entries?')) { mal.entries.updateNew(false); } })) .append(' | full scan: ') .append($('<a href="javascript:void(0);">all</a>').click(function() { if (true === confirm('Recalculate all entries and their relations?')) { mal.entries.update(true); } })) .append(', ') .append($('<a href="javascript:void(0);">new</a>').click(function() { if (true === confirm('Recalculate new entries and their relations?')) { mal.entries.updateNew(true); } })) ) .append(') ').append(mal.content.done) .append(' ').append(mal.content.fail) .appendTo('#content > div:first'); } function pagePanel() { var animeLink = $('<a href="#mr_body" id="mr_link_anime" style="font-size: 11px; font-weight: normal;">Missing Anime Relations</a>'), mangaLink = $('<a href="#mr_body" id="mr_link_manga" style="font-size: 11px; font-weight: normal;">Missing Manga Relations</a>'), links = $('<div class="floatRightHeader"></div>').append(animeLink).append(' - ').append(mangaLink) .appendTo('#panel_left > .container > div.bgColor1:first-of-type + .spaceit'); $('a[id^="mr_link_"]', links).each(function() { var type = this.id.match(/^mr_link_(\w+)$/)[1]; $(this).fancybox({ 'hideOnContentClick': false, 'hideOnOverlayClick': true, 'transitionIn': 'none', 'transitionOut': 'none', 'titleShow': false, 'scrolling': 'no', 'onStart': function() { var listType = $('#mr_list_type', mal.content.body); mal.type = type; if (listType.length === 0 || listType.val() !== mal.type) { if (mal.entries.checkVersion()) { mal.entries.load(); } else { mal.entries.clear(); } mal.content.update(false); } } }); }); } mal.entries = { version: mal.cache, left: {}, graph: {}, title: {}, wrong: {}, ignore: {}, // cache data total: 0, done: 0, fail: 0, right: {}, // for internal use only load: function() { mal.entries.clear(); mal.entries.left = mal.loadValue('mal.entries.left', mal.entries.left); mal.entries.graph = mal.loadValue('mal.entries.graph', mal.entries.graph); mal.entries.title = mal.loadValue('mal.entries.title', mal.entries.title); mal.entries.wrong = mal.loadValue('mal.entries.wrong', mal.entries.wrong); mal.entries.ignore = mal.loadValue('mal.entries.ignore', mal.entries.ignore); }, save: function() { mal.saveValue('mal.entries.version', mal.entries.version); mal.saveValue('mal.entries.left', mal.entries.left); mal.saveValue('mal.entries.graph', mal.entries.graph); mal.saveValue('mal.entries.title', mal.entries.title); mal.saveValue('mal.entries.wrong', mal.entries.wrong); mal.entries.saveIgnore(); }, saveIgnore: function() { mal.saveValue('mal.entries.ignore', mal.entries.ignore); }, clear: function() { mal.entries.total = mal.entries.done = mal.entries.fail = 0; mal.entries.left = {}; mal.entries.graph = {}; mal.entries.title = {}; mal.entries.wrong = {}; }, clearIgnore: function() { mal.entries.ignore = {}; }, checkVersion: function() { return mal.loadValue('mal.entries.version', '') === mal.entries.version; }, isUpdating: function() { return (mal.entries.done + mal.entries.fail) < mal.entries.total; }, getTitle: function(id) { var result = mal.entries.title.hasOwnProperty(id) ? mal.entries.title[id] : ''; return result.length === 0 ? '?' : result; }, update: function(fullRefresh) { if (!mal.entries.isUpdating()) { mal.entries.clear(); mal.entries.updateNew(fullRefresh); } }, updateNew: function(fullRefresh) { if (mal.entries.isUpdating()) { return; } var index = 0, links = $('#content strong + a'); mal.entries.done = mal.entries.fail = 0; mal.entries.total = links.length; mal.entries.right = {}; 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() { var id = parseInt($(this).prop('href').match(/\d+$/)[0]), 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()); } if (mal.entries.left.hasOwnProperty(id)) { mal.content.done.text('done: ' + (++mal.entries.done) + '/' + mal.entries.total); return; } mal.entries.left[id] = true; mal.entries.title[id] = $(this).prev('strong').text().trim(); setTimeout(function() { $.ajax('/' + mal.type + '/' + id) .done(function(data) { var rels = mal.entries.findRelations(id, data); if (!mal.entries.checkRelation(data)) { mal.entries.wrong[id] = true; } if (fullRefresh) { $.each(rels, function(i, id) { mal.entries.right[id] = true; }); } span.html('<small style="color: green;">done</small>'); mal.content.done.text('done: ' + (++mal.entries.done) + '/' + mal.entries.total); if (fullRefresh) { mal.entries.updateFull(); } else { mal.content.update(true); } }) .fail(function() { span.html('<small style="color: red;">fail</small>'); mal.content.fail.text('fail: ' + (++mal.entries.fail)); if (fullRefresh) { mal.entries.updateFull(); } else { mal.content.update(true); } }); }, mal.ajax.delay * (index++)); }); }, updateFull: function() { if (mal.entries.isUpdating()) { return; } $.each(mal.entries.right, function(id) { if (mal.entries.left.hasOwnProperty(id)) { delete mal.entries.right[id]; } }); var index = 0, doneStr = 'done: ' + mal.entries.done + '/' + mal.entries.total, failStr = 'fail: ' + mal.entries.fail; mal.entries.done = mal.entries.fail = 0; mal.entries.total = Object.keys(mal.entries.right).length; if (mal.entries.total === 0) { mal.content.update(true); return; } $.each(mal.entries.right, function(id) { setTimeout(function() { $.ajax('/' + mal.type + '/' + id) .done(function(data) { mal.entries.findRelations(id, data); mal.content.done.text(doneStr + '+' + (++mal.entries.done) + '/' + mal.entries.total); mal.content.update(true); }) .fail(function() { mal.content.fail.text(failStr + '+' + (++mal.entries.fail)); mal.content.update(true); }); }, mal.ajax.delay * (index++)); }); }, comparator: function(a, b) { var aTitle = mal.entries.getTitle(a).toLowerCase(), bTitle = mal.entries.getTitle(b).toLowerCase(); return aTitle.localeCompare(bTitle); }, findRelations: function(lId, data) { var result = [], cnRe = />Related (Anime|Manga)<\/h2>[\s\S]*?<h2>/, 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) { var rId = parseInt(idData[1]); mal.entries.title[rId] = $(this).text().trim(); mal.entries.addRelation(lId, rId); result.push(rId); } }); return result; }, addRelation: function(lId, rId) { if (!mal.entries.graph.hasOwnProperty(lId)) { mal.entries.graph[lId] = []; } if (!mal.entries.graph.hasOwnProperty(rId)) { mal.entries.graph[rId] = []; } if (mal.entries.graph[lId].indexOf(rId) < 0) { mal.entries.graph[lId].push(rId); mal.entries.graph[rId].push(lId); } }, checkRelation: function(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 ((status.match(/Not yet/) && !myStatus.match(/Plan to/)) || (status.match(/(Currently|Publishing)/) && myStatus === 'Completed')) { return false; } if (myStatus !== 'Completed') { return true; } function check(type, selector) { var progRe = new RegExp('>' + type + ':</span>([\\s\\S]*?)</div>', 'i'), myprRe = new RegExp(selector + '[\\s\\S]*?value="(\\d*)"'), prog = data.match(progRe)[1].trim().replace('Unknown', '0'), mypr = data.match(myprRe)[1].trim(); return parseInt(mypr) === parseInt(prog); } if (mal.type === 'anime') { if (!check('Episodes', 'name="myinfo_watchedeps"')) { return false; } } else { if (!check('Volumes', 'id="myinfo_volumes"') || !check('Chapters', 'id="myinfo_chapters"')){ return false; } } return true; }, findComps: function() { var result = {}, used = {}, comp = []; function dfs(v) { used[v] = true; comp.push(v); if (!mal.entries.graph.hasOwnProperty(v)) { return; } $.each(mal.entries.graph[v], function(i, to) { if (!used.hasOwnProperty(to)) { dfs(to); } }); } $.each(mal.entries.graph, function(v) { if (!used.hasOwnProperty(v)) { comp = []; dfs(v); if (comp.length > 1) { result[v] = comp; } } }); return result; } }; mal.content = { body: $('<div id="mr_body"><h2 id="mr_body_title">Missing Relations <span><a id="mr_version" href="http://greasyfork.org/scripts/9261" target="_blank"></a></span></h2></div>'), list: $('<div id="mr_list"></div>'), done: $('<span id="mr_status_done" style="color: green;"></span>'), fail: $('<span id="mr_status_fail" style="color: #c32;"></span>'), setVersion: function(str) { $('#mr_version', mal.content.body).text(str); }, update: function(save) { if (mal.entries.isUpdating()) { return; } if (save === true) { mal.entries.save(); } var title = $('#mr_body_title', mal.content.body), listType = $('<input type="hidden" id="mr_list_type" value="' + mal.type + '" />'); title.html(title.html().replace(/Missing (.*?)Relations/, 'Missing ' + (mal.type === 'anime' ? 'Anime' : 'Manga') + ' Relations')); var comps = mal.entries.findComps(); if (Object.keys(comps).length === 0) { var p = $('<p>No missing relations found.</p>'); if (document.URL.match(/^http:\/\/myanimelist\.net\/panel\.php$/)) { p.append(' ').append('<a href="' + (mal.type === 'anime' ? '/editlist.php?type=anime' : '/panel.php?go=editmanga') + '">Refresh</a>'); } mal.content.list.empty().append(p).append(listType); return; } var table = $('<table id="relTable" border="0" cellpadding="0" cellspacing="0" width="100%"><thead><tr><th>You\'ve ' + (mal.type === 'anime' ? 'watched' : 'read') + ' this…</th><th>…so you might want to check this:</th></tr></table>'), undel = $('<div id="mr_undelete"><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.showIgnoredRelations();">Show them</a></p></div>'), tfoot = $('<tfoot><tr><td class="mr_td_left"><div class="mr_count"><span></span></div></td><td class="mr_td_right"><div class="mr_count"><span></span></div></td></tr></tfoot>'); // red relations var wrong = Object.keys(mal.entries.wrong); if (wrong.length > 0) { var ul = $('<ul></ul>'), size = 0; $.each(wrong.sort(mal.entries.comparator), function(i, id) { mal.content.getLiLeft(id).prop('id', 'mr_li_red_' + id).appendTo(ul); ++size; }); var tbody = $('<tbody></tbody>'), tr = $('<tr class="mr_tr_data"></tr>') .append($('<td class="mr_td_left"></td>').append(ul)) .append($('<td class="mr_td_right"></td>').append(mal.content.getEntryWarning())) .appendTo(tbody); if (size > 5) { tr.addClass('mr_tr_collapsed'); $('<tr class="mr_tr_more"></tr>').append(mal.content.getMoreLink()).insertAfter(tr); } tbody.appendTo(table); } // normal relations $.each(comps, function(i, comp) { var ulLeft = $('<ul></ul>'), ulRight = $('<ul></ul>'), lSize = 0, rSize = 0; $.each(comp.sort(mal.entries.comparator), function(i, id) { if (mal.entries.left.hasOwnProperty(id)) { mal.content.getLiLeft(id).prop('id', 'mr_li_' + id).appendTo(ulLeft); ++lSize; } else { mal.content.getLiRight(id).prop('id', 'mr_li_' + id).appendTo(ulRight); ++rSize; } }); if (lSize > 0 && rSize > 0) { var tbody = $('<tbody></tbody>'), tr = $('<tr class="mr_tr_data"></tr>') .append($('<td class="mr_td_left"></td>').append(ulLeft)) .append($('<td class="mr_td_right"></td>').append(ulRight)) .appendTo(tbody); if (lSize > 5 || rSize > 5) { tr.addClass('mr_tr_collapsed'); $('<tr class="mr_tr_more"></tr>').append(mal.content.getMoreLink()).insertAfter(tr); } tbody.appendTo(table); } }); mal.content.list.empty() .append(undel) .append(table.append(tfoot)) .append(listType); mal.hideIgnoredRelations(true); mal.content.updateLineCount(); }, getLiLeft: function(id) { return $('<li></li>') .append(mal.content.getEntryLink(id, mal.entries.getTitle(id))); }, getLiRight: function(id) { return $('<li></li>') .append(mal.content.getHideButton('window.ignoreRelation(' + id + ');', 'Hide this relation')) .append(mal.content.getEntryLink(id, mal.entries.getTitle(id))); }, getEntryLink: function(id, title) { return $('<a title="' + title + '"href="' + '/' + mal.type + '/' + id + '" target="_blank">' + title + '</a>'); }, getEntryWarning: function() { return $('<div class="mr_warning"><span>Wrong status or ' + (mal.type === 'anime' ? 'episode' : 'vol./chap.') + ' count</span></div>'); }, getHideButton: function(func, title) { return $('<div class="mr_hide"><span><a href="javascript:void(0);" title="' + title + '" onclick="' + func + '">x</a></span></div>'); }, getMoreLink: function() { var result = $('<td colspan="2"></td>'); $('<a class="mr_more" href="javascript:void(0);">show more</a>').click(function() { var tr = $(this).closest('tr'); tr.prev('.mr_tr_data').removeClass('mr_tr_collapsed'); tr.remove(); }).appendTo(result); return result; }, updateLineCount: function() { var count = $('#relTable td.mr_td_right li:not([style*="display: none"])', mal.content.list).length, total = $('#relTable td.mr_td_right li', mal.content.list).length, left = $('#relTable td.mr_td_left li', mal.content.list).length; $('tfoot td.mr_td_left .mr_count span', mal.content.list).text('Total: ' + left); $('tfoot td.mr_td_right .mr_count span', mal.content.list).text('Total: ' + total + ', Visible: ' + count); } }; mal.ignoreRelation = function(id, save) { var li = $('td.mr_td_right li[id="mr_li_' + id + '"]', mal.content.list); if (li.length === 0) { if (mal.entries.ignore.hasOwnProperty(id)) { delete mal.entries.ignore[id]; } if (save) { mal.entries.saveIgnore(); } return; } var row = li.hide().closest('tbody'), count = $('td.mr_td_right li:not([style*="display: none;"])', row).length; row.toggle(count > 0); if (count <= 5) { $('a.mr_more', row).trigger('click'); } mal.entries.ignore[id] = true; if (save) { mal.entries.saveIgnore(); 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(save) { mal.showIgnoredRelations(false); $.each(mal.entries.ignore, function(id) { mal.ignoreRelation(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); if (save) { mal.entries.saveIgnore(); } }; mal.showIgnoredRelations = function(save) { $('#mr_undelete_msg', mal.content.list).hide(); $('li[id^="mr_li_"]', mal.content.list).show(); $('tbody', mal.content.list).show(); if (save) { mal.entries.clearIgnore(); mal.entries.saveIgnore(); } }; mal.loadValue = function(key, value) { try { value = JSON.parse(localStorage.getItem(mal.name + '#' + mal.type + '#' + key)) || value; } catch (e) {} return value; }; mal.saveValue = function(key, value) { localStorage.setItem(mal.name + '#' + mal.type + '#' + key, JSON.stringify(value)); }; window.ignoreRelation = function(id) { mal.ignoreRelation(id, true); mal.content.updateLineCount(); }; window.showIgnoredRelations = function() { mal.showIgnoredRelations(true); mal.content.updateLineCount(); }; $('<style type="text/css"></style>').html( '#content span[class^="mr_status_id_"] { float: right; padding: 0 7px; }' + 'div#mr_body { text-align: center; width: 670px; 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: 16px; font-weight: normal; text-align: center; margin: 0 0 8px 0; position: relative; border: 0; }' + 'div#mr_body #mr_body_title span { font-size: 12px; 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: 20px; font-size: 13px; }' + 'div#mr_list { width: auto; height: 680px; overflow-x: hidden; overflow-y: auto; margin: 18px auto 0; }' + 'div#mr_list #relTable { border: 1px solid #f5f5f5; }' + 'div#mr_list #relTable thead { background-color: #f5f5f5; }' + 'div#mr_list #relTable th { background-color: transparent; width: 50%; padding: 5px 0 5px 6px; font-size: 12px; font-weight: bold; text-align: left; line-height: 20px !important; box-shadow: none; }' + 'div#mr_list #relTable tbody { background-color: #fff; }' + 'div#mr_list #relTable tbody:hover { background-color: #f5f5f5; }' + 'div#mr_list #relTable tbody tr:first-of-type td { box-shadow: 0px 1em 1em -1em #ddd inset; }' + 'div#mr_list #relTable td { background-color: transparent; width: 50%; padding: 5px 0 5px 6px; font-size: 13px; font-weight: normal; text-align: left; line-height: 20px !important; vertical-align: top; }' + 'div#mr_list #relTable td div span { line-height: 20px !important; }' + 'div#mr_list #relTable td ul { list-style-type: none; margin: 0; padding: 0; }' + 'div#mr_list #relTable tr.mr_tr_collapsed td ul { height: 100px; overflow-y: hidden; }' + 'div#mr_list #relTable td ul li { width: 100%; padding: 0; margin: 0; }' + 'div#mr_list #relTable td ul li > a { display: block; width: 295px !important; line-height: 20px !important; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }' + 'div#mr_list #relTable td.mr_td_left ul li > a { width: 310px !important; }' + 'div#mr_list #relTable tfoot td { border-top: 1px solid #f5f5f5; }' + 'div#mr_list #relTable td .mr_count { color: #666; font-size: 11px; font-weight: normal; text-align: left; }' + 'div#mr_list #relTable td .mr_warning { width: 260px; color: #e43; font-size: 12px; font-weight: bold; text-align: left; }' + 'div#mr_list #relTable td .mr_hide { display: inline-block !important; width: 15px; float: right; text-align: left; font-size: 11px; }' + 'div#mr_list #relTable td .mr_hide a { color: #888 !important; line-height: 20px !important; font-style: normal !important; text-decoration: none !important; }' + 'div#mr_list #relTable tr.mr_tr_more td { padding: 0 0 2px 0; }' + 'div#mr_list #relTable td .mr_more { display: block !important; text-align: center; color: #c0c0c0 !important; font-style: normal !important; font-size: 0.9em; text-decoration: none !important; }' ).appendTo('head'); main(); })(jQuery);