Wanikani Level Up Time

Calculates what day and time you can level up if your reviews are correct.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Wanikani Level Up Time
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  Calculates what day and time you can level up if your reviews are correct.
// @author       Reeko182 - Instagram @joelabad
// @include      https://www.wanikani.com/dashboard
// @include      https://www.wanikani.com/
// @grant        none
// ==/UserScript==

window.lu = {};

(function(lu_obj) {

    //===================================================================
    // Initialization of the Wanikani Open Framework.
    //-------------------------------------------------------------------
    var script_name = 'Wanikani Level Up Time';
    var wkof_version_needed = '1.0.27';
    if (!window.wkof) {
        if (confirm(script_name+' requires Wanikani Open Framework.\nDo you want to be forwarded to the installation instructions?'))
            window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
        return;
    }
    if (wkof.version.compare_to(wkof_version_needed) === 'older') {
        if (confirm(script_name+' requires Wanikani Open Framework version '+wkof_version_needed+'.\nDo you want to be forwarded to the update page?'))
            window.location.href = 'https://greasyfork.org/en/scripts/38582-wanikani-open-framework';
        return;
    }

    wkof.include('ItemData');
    wkof.ready('document,ItemData').then(lu_startup);

    //========================================================================
    // Startup
    //-------------------------------------------------------------------
    function lu_startup() {
        install_css();
        init_ui();

        wkof.ItemData.get_items({
            wk_items:{
                options:{
                    assignments:true,
                    review_statistics:true
                },
                filters:{
                    level:'+0',
                    item_type:'radical,kanji',
                }
            }
        })
        .then(lu_process_items);
    }

    //========================================================================
    // CSS Styling
    //-------------------------------------------------------------------

    var lu_css =
        '#lu_container { margin: -20px auto 32px auto; color: #555; font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; font-weight: 300; text-shadow: 0 1px 0 #fff; font-size:13px; }'+
        '#lu_container i { padding-right:4px; font-size:14px; }';

    //========================================================================
    // Install stylesheet.
    //-------------------------------------------------------------------
    function install_css() {
        $('head').append('<style>'+lu_css+'</style>');
    }

    //========================================================================
    // Initialize the user interface.
    //-------------------------------------------------------------------
    function init_ui() {

        var html =

            "<div id='lu_container'>"+
            "<i class='icon-circle-arrow-up'></i> <b>Level Up:</b> <span id='lu_date'></span>"+
            "</div>";

        $('section.progression').after(html);

    }

    //========================================================================
    // Populate level info from API.
    //-------------------------------------------------------------------
    function lu_process_items(data) {

        lu_obj.items = wkof.ItemData.get_index(data, 'item_type');

        var guruDate;

        var lastRadical = lastItem('radical');
        //console.log(lastRadical);
        if(!lastRadical) //If all radicals are not unlocked
        {
            guruDate = getGuruDate("radical",false);
        }
        else
        {

            if(lastRadical[0] < 5) //If any radical is not gurued (stage < 5)
            {
                //Find out last radical guru date
                guruDate = getGuruDate("radical",lastRadical);
            }
            else
            {
                var lastKanji = lastItem('kanji');
                //console.log(lastKanji);
                if(!lastKanji) //If 90% of kanjis are not unlocked
                {
                    guruDate = getGuruDate("kanji",false);
                }
                else
                {
                    //Find out last kanji guru date
                    guruDate = getGuruDate("kanji",lastKanji);
                }

            }

        }

        var luDateDiv = document.getElementById('lu_date');
        luDateDiv.innerHTML += guruDate;

    }

    //========================================================================
    // Get item guru date
    //-------------------------------------------------------------------
    function getGuruDate(itype,item){

        //Calculate days to guru

        var daysToGuru = 0;

        if(!item)
        {
            daysToGuru = 4+8+23+47;
        }
        else
        {
            if(item[0] < 1) daysToGuru += 4;
            if(item[0] <= 1) daysToGuru += 8;
            if(item[0] <= 2) daysToGuru += 23;
            if(item[0] <= 3) daysToGuru += 47;
        }

        if(itype == "radical") daysToGuru += 4+8+23+47;

        //Add the days to the available or current date if item is locked

        var guruDate = (item) ? new Date(item[1]) : new Date();
        //console.log(guruDate);
        guruDate.setHours(guruDate.getHours() + daysToGuru);
        //console.log(guruDate);

        return ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'][guruDate.getDay()]+', '+
               ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][guruDate.getMonth()]+' '+guruDate.getDate()+', '+
               ('0'+guruDate.getHours()).slice(-2)+':'+('0'+guruDate.getMinutes()).slice(-2)+"h";

    }

    //========================================================================
    // Sort Array by multiple subarray fields.
    //-------------------------------------------------------------------
    function getSortMethod(){
        var _args = Array.prototype.slice.call(arguments);
        return function(a, b){
            for(var x in _args){
                var ax = a[_args[x].substring(1)];
                var bx = b[_args[x].substring(1)];
                var cx;

                ax = typeof ax == "string" ? ax.toLowerCase() : ax / 1;
                bx = typeof bx == "string" ? bx.toLowerCase() : bx / 1;

                if(_args[x].substring(0,1) == "-"){cx = ax; ax = bx; bx = cx;}
                if(ax != bx){return ax < bx ? -1 : 1;}
            }
        }
    }

    //========================================================================
    // returns last item info by type
    //-------------------------------------------------------------------
    function lastItem(itype) {

        var itemData = wkof.ItemData.get_index(lu_obj.items[itype], 'subject_id');
        //console.log(itemData);

        var items = [];
        var itemsCount = 0;
        for (var i in itemData) {
            if(itemData[i].assignments) items.push({slug:itemData[i].data.slug, stage:itemData[i].assignments.srs_stage, available:itemData[i].assignments.available_at});
            itemsCount++;
        }
        items = items.sort(getSortMethod('+stage','-available','+slug'));

        console.log(items);
        //console.log(itype+":"+items[0].stage+":"+items[0].available);
        //console.log(itemsCount+":"+items.length+":"+(items.length*100/itemsCount));

        switch(itype)
        {
            case "radical": return (items.length == itemsCount) ? [items[0].stage, items[0].available] : false; break;
            case "kanji":

                var kanjiRequired = Math.ceil(itemsCount*0.9);
                //console.log(kanjiRequired+" out of "+itemsCount+" kanjis required");

                if (items.length >= kanjiRequired)
                {

                    if((itemsCount-kanjiRequired) > 0) items.splice(((itemsCount-kanjiRequired)*-1), (itemsCount-kanjiRequired));
                    return [items[0].stage, items[0].available];

                }
                else
                {
                    return false;
                }

        }

    }

})(window.lu);