您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Mass Resurrect/Retire of Burn items on WaniKani
当前为
// ==UserScript== // @name Wanikani Burn Manager // @namespace rfindley // @description Mass Resurrect/Retire of Burn items on WaniKani // @version 1.0.1 // @include https://www.wanikani.com/* // @exclude https://www.wanikani.com/lesson* // @exclude https://www.wanikani.com/review* // @copyright 2016+, Robin Findley // @license MIT; http://opensource.org/licenses/MIT // @run-at document-end // @grant none // ==/UserScript== wkburnmgr = {}; (function(gobj) { var settings = { fetch: { levels_per: 5, retries: 3, timeout: 10000, simultaneous: 5 }, retire: { simultaneous: 5 } }; var mgr_added = false; var busy = false; var fetched = {rad:[], kan:[], voc:[]}, items = {rad:[], kan:[], voc:[]}, item_info={rad:{}, kan:{}, voc:{}}; var srslvls = ['Apprentice 1','Apprentice 2','Apprentice 3','Apprentice 4','Guru 1','Guru 2','Master','Enlightened','Burned']; var user_level = 0, apikey=''; gobj.fetched = fetched; gobj.items = items; gobj.item_info = item_info; //============================================================================ // Asynchronous serialization and parallelization. (c) 2016 Robin Findley //---------------------------------------------------------------------------- // Functions: // do_parallel_n(n, [func], list); // Do N items at a time. // do_parallel([func], list); // Do all items at same time. // do_serial([func], list); // Do 1 item one at a time. // // Parameters: // n - Number of items to do at a time. // func - If present, single function to run once for each item in list. // list - If func is present, each element is args passed to func. // If func is absent, each element is a function to call. // You can use .bind() on functions to pass parameters (see bind()). // // Example: // do_serial([ // initialize, // do_parallel_n(2, fetch, [ // 'http://www.example.com/page1', // 'http://www.example.com/page2', // 'http://www.example.com/page3' // ]), // parse_pages, // do_parallel([ // setTimeout(console.log.bind(console,'Delay 1'), 500), // setTimeout(console.log.bind(console,'Delay 2'),1000), // setTimeout(console.log.bind(console,'Delay 3'),1500) // ]) // ]) // .then(console.log.bind(console,'Done!')); // .fail(console.log.bind(console,'Fail!')); //---------------------------------------------------------------------------- function do_parallel_n(_n, _func, _list) { var self = this, promise = $.Deferred(), next = 0, simul = _n, completed = 0, mode = 0, result = [], list, func; if (typeof _func === 'function') { mode = 1; func = _func; list = _list; } else { mode = 0; list = _func; } if (simul <= 0) simul = list.length; if (list.length === 0) return promise.resolve(); function do_one(value) { if (promise.state() === 'rejected') return; var n = next++; var p; if (mode === 0) { p = list[n](value); } else { var args = list[n]; if (!Array.isArray(args)) args = [args]; if (simul===1) args.push(value); p = func.apply(self,args); } if ((typeof p !== 'object') || (typeof p.then !== 'function')) finish(p); else p.then(finish, fail); function finish(value) { result[n] = value; if (value === false) return promise.reject(result); if (next < list.length) do_one(value); if (++completed === list.length) promise.resolve(result); } function fail(value) { result[n] = value; promise.reject(result); } } for (var i = 0; i < simul && next < list.length; i++) do_one(); return promise; } //---------------------------------------------------------------------------- function do_parallel(_func, _list) {return do_parallel_n.call(this, -1, _func, _list);} function do_serial(_func, _list) {return do_parallel_n.call(this, 1, _func, _list);} //============================================================================ // Fetch a document from the server. //---------------------------------------------------------------------------- function ajax_retry(url, retries, timeout, callback, _options) { var promise = $.Deferred(); retries = retries || 1; timeout = timeout || 120000; var options = { url: url, timeout: timeout }; if (typeof callback === 'function') { // if (debug.dev_server) options.dataType = 'json'; // else // options.dataType = 'jsonp'; } $.extend(options, _options); function action() { $.ajax(options) .done(function(data, status){ // Call optional callback. if (typeof callback === 'function') callback(url, status, data); // Resolve or reject if (status === 'success') promise.resolve(data); else promise.reject(); }) .fail(function(xhr, status, error){ // Try again? if (status === 'error' && --retries > 0) return action(); // Call optional callback, then reject. if (typeof callback === 'function') callback(url, status, error, xhr); promise.reject(); }); } action(); return promise; } //------------------------------------------------------------------- // Convert level-selection string to array. //------------------------------------------------------------------- function levels_from_string(str) { var levels = []; str = str.replace(' ',''); $.each(str.split(','), function(idx, val){ var dash = val.split('-'); if (dash.length < 1 || dash.length > 2) return; var n1 = Number(dash[0]); var n2 = n1; if (dash.length === 2) n2 = Number(dash[1]); if (isNaN(n1) || isNaN(n2) || n1 < 1 || n2 < 1 || n1 > n2 || n1 > 200 || n2 > 200) return []; for (var n = n1; n <= n2; n++) levels[n] = 1; }); return levels; } //------------------------------------------------------------------- // Convert level-selection array to string. //------------------------------------------------------------------- function levels_to_string(arr) { var levels = []; var n1 = 1, n2, cnt = arr.length; while (n1 <= cnt) { if (arr[n1] === 1) { n2 = n1+1; while (n2 <= cnt && arr[n2] ===1) n2++; n2--; if (n1 == n2) levels.push(n1); else levels.push(n1+'-'+n2); n1 = n2; } n1++; } return levels.join(','); } //------------------------------------------------------------------- // Add a <style> section to the document. //------------------------------------------------------------------- function addStyle(aCss) { var head, style; head = document.getElementsByTagName('head')[0]; if (head) { style = document.createElement('style'); style.setAttribute('type', 'text/css'); style.textContent = aCss; head.appendChild(style); return style; } return null; } //------------------------------------------------------------------- // Display the Burn Manager object. //------------------------------------------------------------------- function add_mgr() { var html = '<div id="burn_mgr"><div id="burn_mgr_box" class="container">'+ '<h3 class="small-caps invert">Burn Manager <span id="burn_mgr_instr" href="#">[ Instructions ]</span></h3>'+ '<form accept-charset="UTF-8" action="#" class="form-horizontal"><fieldset class="additional-info">'+ // Instructions ' <div class="instructions">'+ ' <div class="header small-caps invert">Instructions</div>'+ ' <div class="content">'+ ' <p>Enter your Resurrect/Retire criteria below, then click <span class="btn">Preview</span>.<br>A preview window will open, showing burn items matching the Level and Type criteria.<br>'+ 'You can change your criteria at any time, then click <span class="btn">Preview</span> again to update your settings... but any <b>manually toggled changes will be lost</b>.</p>'+ ' <p class="nogap">In the preview window:</p>'+ ' <ul>'+ ' <li><b>Hover</b> over an item to see <b>item details</b>.</li>'+ ' <li><b>Click</b> an item to <b>toggle</b> its desired state between <b>Resurrect</b> and <b>Retired</b>.</li>'+ ' </ul>'+ ' <p>After you have adjusted all items to their desired state, click <span class="btn">Execute</span> to begin changing you item statuses<br>'+ 'While executing, please allow the progress bar to reach 100% before navigating to another page, otherwise some items will not be Resurrected or Retired.</p>'+ ' <span class="rad">十</span><span class="kan">本</span><span class="voc">本当</span> = Will be Resurrected<br>'+ ' <span class="rad inactive">十</span><span class="kan inactive">本</span><span class="voc inactive">本当</span> = Will be Retired'+ ' </div>'+ ' </div>'+ // Settings ' <div class="control-group">'+ ' <label class="control-label" for="burn_mgr_levels">Level Selection:</label>'+ ' <div class="controls">'+ ' <input id="burn_mgr_levels" type="text" autocomplete="off" class="span6" max_length=255 name="burn_mgr[levels]" placeholder="Levels to resurrect or retire (e.g. "1-3,5")" value>'+ ' </div>'+ ' </div>'+ ' <div class="control-group">'+ ' <label class="control-label">Item types:</label>'+ ' <div id="burn_mgr_types" class="controls">'+ ' <label class="checkbox inline"><input id="burn_mgr_rad" name="burn_mgr[rad]" type="checkbox" value="1" checked="checked">Radicals</label>'+ ' <label class="checkbox inline"><input id="burn_mgr_kan" name="burn_mgr[kan]" type="checkbox" value="1" checked="checked">Kanji</label>'+ ' <label class="checkbox inline"><input id="burn_mgr_voc" name="burn_mgr[voc]" type="checkbox" value="1" checked="checked">Vocab</label>'+ ' </div>'+ ' </div>'+ ' <div class="control-group">'+ ' <label class="control-label" for="burn_mgr_initial">Action / Initial State:</label>'+ ' <div id="burn_mgr_initial" class="controls">'+ ' <label class="radio inline"><input id="burn_mgr_initial_current" name="burn_mgr[initial]" type="radio" value="0" checked="checked">No change / Current state</label>'+ ' <label class="radio inline"><input id="burn_mgr_initial_resurrect" name="burn_mgr[initial]" type="radio" value="1">Resurrect All</label>'+ ' <label class="radio inline"><input id="burn_mgr_initial_retire" name="burn_mgr[initial]" type="radio" value="2">Retire All</label>'+ ' </div>'+ ' </div>'+ ' <div class="control-group">'+ ' <div id="burn_mgr_btns" class="controls">'+ ' <a id="burn_mgr_preview" href="#burn_mgr_preview" class="btn btn-mini">Preview</a>'+ ' <a id="burn_mgr_execute" href="#burn_mgr_execute" class="btn btn-mini">Execute</a>'+ ' <a id="burn_mgr_close" href="#burn_mgr_close" class="btn btn-mini">Close</a>'+ ' </div>'+ ' </div>'+ // Preview ' <div class="status"><div class="message controls"></div></div>'+ ' <div class="preview"></div>'+ ' <div id="burn_mgr_item_info" class="hidden"></div>'+ '</fieldset>'+ '</form>'+ '<hr>'+ '</div></div>'; var css = '#burn_mgr {display:none;}'+ '#burn_mgr_instr {margin-left:20px; font-size:0.8em; opacity:0.8; cursor:pointer;}'+ '#burn_mgr .instructions {display:none;}'+ '#burn_mgr .instructions .content {padding:5px;}'+ '#burn_mgr .instructions p {font-size:13px; line-height:17px; margin-bottom:1.2em;}'+ '#burn_mgr .instructions p.nogap {margin-bottom:0;}'+ '#burn_mgr .instructions ul {margin-left:16px; margin-bottom:1.2em;}'+ '#burn_mgr .instructions li {font-size:13px; line-height:17px;}'+ '#burn_mgr .instructions span {cursor:default;}'+ '#burn_mgr .instructions .btn {color:#000; padding:0px 3px 2px 3px;}'+ '#burn_mgr .noselect {-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;}'+ '#burn_mgr h3 {'+ ' margin-top:10px; margin-bottom:0px; padding:0 30px; border-radius: 5px 5px 0 0;'+ ' background-color: #fbc042;'+ ' background-image: -moz-linear-gradient(-45deg, #fbc550, #faac05);'+ ' background-image: -webkit-linear-gradient(-45deg, #fbc550, #faac05);'+ ' background-image: -o-linear-gradient(-45deg, #fbc550, #faac05);'+ ' background-image: linear-gradient(-45deg, #fbc550, #faac05);'+ '}'+ '#burn_mgr form {border-radius:0 0 5px 5px; margin-bottom:10px;}'+ '#burn_mgr #burn_mgr_box fieldset {border-radius:0 0 5px 5px; margin-bottom:0px; padding:10px;}'+ '#burn_mgr .control-group {margin-bottom:10px;}'+ '#burn_mgr .controls .inline {padding-right:10px;}'+ '#burn_mgr .controls .inline input {margin-left:-15px;}'+ '#burn_mgr_btns .btn {width:50px; margin-right:10px;}'+ '#burn_mgr .status {display:none;}'+ '#burn_mgr .status .message {display:inline-block; background-color:#ffc; padding:2px 10px; font-weight:bold; border:1px solid #999; min-width:196px;}'+ '#burn_mgr .preview {display:none;}'+ '#burn_mgr .header {padding:0px 3px; line-height:1.2em; margin:0px;}'+ '#burn_mgr .preview .header .count {text-transform:none; margin-left:10px;}'+ '#burn_mgr .content {padding:0px 2px 2px 2px; border:1px solid #999; border-top:0px; background-color:#fff; margin-bottom:10px; position:relative;}'+ '#burn_mgr .content span {'+ ' color:#fff;'+ ' font-size:13px;'+ ' line-height:13px;'+ ' margin:0px 1px;'+ ' padding:2px 3px 3px 2px;'+ ' border-radius:4px;'+ ' box-shadow:0 -2px 0 rgba(0,0,0,0.2) inset;'+ ' display:inline-block;'+ '}'+ '#burn_mgr .rad {background-color:#0096e7; background-image:linear-gradient(to bottom, #0af, #0093dd);}'+ '#burn_mgr .kan {background-color:#ee00a1; background-image:linear-gradient(to bottom, #f0a, #dd0093);}'+ '#burn_mgr .voc {background-color:#9800e8; background-image:linear-gradient(to bottom, #a0f, #9300dd);}'+ '#burn_mgr .rad.inactive {background-color:#c3e3f3; background-image:linear-gradient(to bottom, #d4ebf7, #c3e3f3);}'+ '#burn_mgr .kan.inactive {background-color:#f3c3e3; background-image:linear-gradient(to bottom, #f7d4eb, #f3c3e3);}'+ '#burn_mgr .voc.inactive {background-color:#e3c3f3; background-image:linear-gradient(to bottom, #ebd4f7, #e3c3f3);}'+ '#burn_mgr .preview .content span {cursor:pointer;}'+ '#burn_mgr_item_info {'+ ' position: absolute;'+ ' padding:8px;'+ ' color: #eeeeee;'+ ' background-color:rgba(0,0,0,0.8);'+ ' border-radius:8px;'+ ' font-family:"Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;'+ ' font-weight: bold;'+ ' z-index:3;'+ '}'+ '#burn_mgr_item_info .item {font-size:2em; line-height:1.2em;}'+ '#burn_mgr_item_info .item img {height:1em; width:1em; vertical-align:bottom;}'+ '#burn_mgr_item_info>div {padding:0 8px; background-color:#333333;}'+ '#burn_mgr hr {border-top-color:#bbb; margin-top:0px; margin-bottom:0px;}'; addStyle(css); $(html).insertAfter($('.navbar')); // Add event handlers $('#burn_mgr_preview').on('click', on_preview); $('#burn_mgr_execute').on('click', on_execute); $('#burn_mgr_close').on('click', on_close); $('#burn_mgr_instr').on('click', on_instructions); mgr_added = true; } //------------------------------------------------------------------- // Toggle the Burn Manager open/closed. //------------------------------------------------------------------- function toggle_mgr(e) { if (e !== undefined) e.preventDefault(); // Add the manager if not already. if (!mgr_added) add_mgr(); $('#burn_mgr').slideToggle(); $('html, body').animate({scrollTop:0},800); } //------------------------------------------------------------------- // Fetch API key from account page. //------------------------------------------------------------------- function get_apikey() { var promise = $.Deferred(); apikey = localStorage.getItem('apiKey'); if (typeof apikey === 'string' && apikey.length == 32) return promise.resolve(); $('#burn_mgr .status .message').html('Fetching API key...'); $('#burn_mgr .status').slideDown(); ajax_retry('/account') .then(function(page) { // --[ SUCCESS ]---------------------- // Make sure what we got is a web page. if (typeof page !== 'string') {return reject();} // Extract the user name. page = $(page); // Extract the API key. apikey = page.find('#api-button').parent().find('input').attr('value'); if (typeof apikey !== 'string' || apikey.length !== 32) {return reject();} localStorage.setItem('apiKey', apikey); promise.resolve(); }).fail(function(result) { // --[ FAIL ]------------------------- $('#burn_mgr .status .message').html('Fetching API key... FAILED!'); promise.reject(new Error('Failed to fetch API key!')); }); return promise; } //------------------------------------------------------------------- // Event handler for item click. //------------------------------------------------------------------- function item_click_event(e) { $(e.currentTarget).toggleClass('inactive'); } //------------------------------------------------------------------- // Event handler for item hover info. //------------------------------------------------------------------- function item_info_event(e) { var hinfo = $('#burn_mgr_item_info'); var target = $(e.currentTarget); switch (e.type) { //----------------------------- case 'mouseenter': var itype = target.data('type'); var ref = target.data('ref'); var item = item_info[itype][ref]; var status = (can_resurrect(item)===true ? 'Retired' : 'Resurrected'); var str = '<div class="'+itype+'">'; switch (itype) { case 'rad': str += '<span class="item">Item: <span lang="ja">'; if (item.character !== null) str += item.character+'</span></span><br />'; else str += '<img src="'+item.image+'" /></span></span><br />'; str += 'Meaning: '+toTitleCase(item.meaning)+'<br />'; break; case 'kan': str += '<span class="item">Item: <span lang="ja">'+item.character+'</span></span><br />'; str += toTitleCase(item.important_reading)+': <span lang="ja">'+item[item.important_reading]+'</span><br />'; str += 'Meaning: '+toTitleCase(item.meaning)+'<br />'; if (item.user_specific.user_synonyms !== null && item.user_specific.user_synonyms.length > 0) str += 'Synonyms: '+toTitleCase(item.user_specific.user_synonyms.join(', '))+'<br />'; break; case 'voc': str += '<span class="item">Item: <span lang="ja">'+item.character+'</span></span><br />'; str += 'Reading: <span lang="ja">'+item.kana+'</span><br />'; str += 'Meaning: '+toTitleCase(item.meaning)+'<br />'; if (item.user_specific.user_synonyms !== null && item.user_specific.user_synonyms.length > 0) str += 'Synonyms: '+toTitleCase(item.user_specific.user_synonyms.join(', '))+'<br />'; break; } str += 'Level: '+item.level+'<br />'; str += 'SRS Level: '+srslvls[item.user_specific.srs_numeric-1]+'<br />'; str += 'Currently: '+status+'<br />'; str += '</div>'; hinfo.html(str); hinfo.css('left', target.offset().left - target.position().left); hinfo.css('top', target.offset().top + target.outerHeight() + 3); hinfo.removeClass('hidden'); break; //----------------------------- case 'mouseleave': hinfo.addClass('hidden'); break; } } //------------------------------------------------------------------- // Make first letter of each word upper-case. //------------------------------------------------------------------- function toTitleCase(str) { return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); } //------------------------------------------------------------------- // Read the user's "initial state" setting. //------------------------------------------------------------------- function read_initial_state() { return Number($('#burn_mgr_initial input:checked').val()); } //------------------------------------------------------------------- // Run when user clicks 'Preview' button //------------------------------------------------------------------- function on_preview(e, refresh) { if (refresh !== true) e.preventDefault(); if (busy) return; busy = true; // Read the user's level selection criteria var levels = levels_from_string($('#burn_mgr_levels').val()); // Convert the input field to normalized form $('#burn_mgr_levels').val(levels_to_string(levels)); // Fetch data load_data(levels) .then(function(){ // Hide the "Loading" message. busy = false; $('#burn_mgr .status').slideUp(); var html = ''; var itypes = ['rad', 'kan', 'voc']; var state = read_initial_state(); if (refresh === true) state = 0; var get_initial = [ /* 0 */ function(item) {return (can_retire(item)===true);}, // Show current item state. /* 1 */ function(item) {return true;}, // Mark all items for resurrection. /* 2 */ function(item) {return false;}, // Mark all items for retirement. ][state]; for (var level = 1; level <= user_level; level++) { if (levels[level] !== 1) continue; var active = {rad:0, kan:0, voc:0}; var total = {rad:0, kan:0, voc:0}; var item_html = ''; $.each(itypes, function(idx, itype){ // Skip item types that aren't checked. var subact=0, subtot=0; var list = items[itype][level]; if (!$('#burn_mgr_'+itype).is(':checked')) return; if (list === undefined) return; $.each(list,function(idx,item){ var text, ref, state; if (itype === 'rad') { if (item.image !== null) text = '<i class="radical-'+item.meaning+'"></i>'; else text = item.character; } else { text = item.character; } ref = item.ref; if (get_initial(item)) { state = ''; subact++; } else { state = ' inactive'; } subtot++; item_html += '<span class="'+itype+state+'" data-type="'+itype+'" data-ref="'+ref+'">'+text+'</span>'; }); active[itype] += subact; total[itype] += subtot; }); html += '<div class="header small-caps invert">Level '+level+ // ' <span class="count">Resurrected: [ R: '+active.rad+'/'+total.rad+', K: '+active.kan+'/'+total.kan+', V: '+active.voc+'/'+total.voc+' ]</span>'+ '</div>'+ '<div class="content level noselect">'+ item_html+ '</div>'; } $('#burn_mgr .preview').html(html).slideDown(); $('#burn_mgr .preview .content.level') .on('mouseenter', 'span', item_info_event) .on('mouseleave', item_info_event) .on('click', 'span', item_click_event); }); } //------------------------------------------------------------------- // Run when user clicks 'Execute' button //------------------------------------------------------------------- function on_execute(e) { e.preventDefault(); if (busy) return; busy = true; var status = $('#burn_mgr .status'), message = $('#burn_mgr .status .message'); // Read the user's level selection criteria var levels = levels_from_string($('#burn_mgr_levels').val()); // Convert the input field to normalized form $('#burn_mgr_levels').val(levels_to_string(levels)); // Fetch data load_data(levels) .then(function(){ var task_list = []; var xlat = {rad:'radical',kan:'kanji',voc:'vocabulary'}; var use_preview = $('#burn_mgr .preview').is(':visible'); if (use_preview) { $('#burn_mgr .preview .content span').each(function(idx,elem){ elem = $(elem); var ref = elem.data('ref'); var itype = elem.data('type'); var item = item_info[itype][ref]; var current = can_resurrect(item); var want = elem.hasClass('inactive'); if (current != want) task_list.push({url:'/retired/'+xlat[itype]+'/'+ref+'?'+(want?'retire':'resurrect')+'=true',item:item}); }); } else { // Don't use Preview information. var state = read_initial_state(); if (state !== 0) { $.each(['rad','kan','voc'], function(idx, itype){ if (!$('#burn_mgr_'+itype).is(':checked')) return; $.each(item_info[itype], function(idx, item){ var ref = item.ref; var current = can_resurrect(item); var want = (state===2); if (current != want) task_list.push({url:'/retired/'+xlat[itype]+'/'+ref+'?'+(want?'retire':'resurrect')+'=true',item:item}); }); }); } } var tot = task_list.length; var cnt = 0; function task_done(item, url, status, text) { if (status !== 'success') return; message.html('Working... ('+(++cnt)+' of '+tot+')'); var lines = text.split('\n'); var idx; for (idx = lines.length-1; idx >= 0; idx--) { var line = lines[idx]; if (line.match(/var progression/) === null) continue; var json = JSON.parse(line.match(/(\{.*\})/g)).requested_information; item.user_specific.burned = json.burned; item.user_specific.burned_date = json.burned_date; item.user_specific.available_date = json.available_date; item.user_specific.srs = json.srs_level; break; } } function retire(task) { return ajax_retry(task.url, settings.retire.retries, settings.retire.timeout, task_done.bind(null,task.item), { type:'POST', data:'_method=put', dataType:'text' } ); } function finish() { message.html('Done! ('+cnt+' of '+tot+')'); busy = false; on_preview(null, true /* refresh */); } message.html('Executing 0 / '+tot); status.slideDown(); do_serial([ do_parallel_n.bind(null,settings.retire.simultaneous, retire, task_list), finish ]); }); } //------------------------------------------------------------------- // Run when user clicks 'Close' button //------------------------------------------------------------------- function on_close(e) { e.preventDefault(); $('#burn_mgr').slideUp(); } //------------------------------------------------------------------- // Run when user clicks 'Instructions' //------------------------------------------------------------------- function on_instructions(e) { e.preventDefault(); $('#burn_mgr .instructions').slideToggle(); } //------------------------------------------------------------------- // Return 'true' if item can be retired. //------------------------------------------------------------------- function can_retire(item){ if (item.user_specific === null) return false; if (item.user_specific.srs !== 'burned' && item.user_specific.burned_date !== 0) return true; return false; } //------------------------------------------------------------------- // Return 'true' if item can be resurrected. //------------------------------------------------------------------- function can_resurrect(item){ if (item.user_specific === null) return false; if (item.user_specific.srs === 'burned') return true; return false; } //------------------------------------------------------------------- // Return 'true' if item has been burned. //------------------------------------------------------------------- function has_burned(item){ if (item.user_specific === null) return false; if (item.user_specific.srs === 'burned') return true; if (item.user_specific.burned_date !== 0) return true; return false; } //------------------------------------------------------------------- // Fetch API data for any requested levels that aren't yet fetched //------------------------------------------------------------------- function load_data(levels) { var criteria = []; var status = $('#burn_mgr .status'), message = $('#burn_mgr .status .message'); var percent, total, done; var itypes = ['radicals', 'kanji', 'vocabulary']; $.each(itypes, function(idx, itype){ // Skip item types that aren't checked. var itype3 = itype.slice(0,3); if (!$('#burn_mgr_'+itype3).is(':checked')) return; var needed = {itype:itype, levels:[]}; for (level = 1; level <= user_level; level++) { if (levels[level] !== 1 || fetched[itype3][level] === 1) continue; needed.levels.push(level); if (itype3 !== 'rad' && needed.levels.length === settings.fetch.levels_per) { criteria.push([needed]); needed = {itype:itype, levels:[]}; } } if (needed.levels.length > 0) criteria.push([needed]); }); function received(criteria, url, status, json) { if (status !== 'success') return; done++; percent.html(Math.floor(done/total*100)+'%'); // Mark which levels we've fetched, so we don't re-fetch on later queries. var itype = criteria.itype.slice(0,3); $.each(criteria.levels, function(idx, level){ fetched[itype][level] = 1; }); $.each(json.requested_information, function(idx, item){ if (!has_burned(item)) return; if (itype === 'rad') item.ref = item.meaning; else item.ref = item.character; var level_items = items[itype][item.level]; if (level_items === undefined) { level_items = []; items[itype][item.level] = level_items; } level_items.push(item); item_info[itype][item.ref] = item; }); } function fetch(criteria) { var url = 'https://www.wanikani.com/api/user/'+apikey+'/'+criteria.itype+'/'+criteria.levels.join(','); return ajax_retry(url, settings.fetch.retries, settings.fetch.timeout, received.bind(null,criteria)); } total = criteria.length; done = 0; if (total === 0) return $.Deferred().resolve(); message.html('Loading item data: <span class="percent">0%</span>'); status.slideDown(); percent = $('#burn_mgr .status .percent'); return do_serial([ get_apikey, do_parallel_n.bind(null,settings.fetch.simultaneous, fetch, criteria) ]); } //------------------------------------------------------------------- // Startup. Runs at document 'load' event. //------------------------------------------------------------------- function startup() { // Make sure we have a top-menu, req'd for insertion. if ($('.navbar').length === 0) return; user_level = Number($('.levels span:nth(0)').text()); // Insert a menu link in the drop-down menu. $('<li><a href="#burnmgr">Burn Manager</a></li>') .insertBefore($('.account .dropdown-menu .nav-header:eq(1)')) .on('click', toggle_mgr); } // Run startup() after window.onload event. if (document.readyState === 'complete') startup(); else window.addEventListener("load", startup, false); })(wkburnmgr);