WaniKani Dashboard Leech List Unburnable Edit

Shows top leeches on dashboard (replaces critical items) and all leeches on a dedicated page (replaces critical items)

// ==UserScript==
// @name          WaniKani Dashboard Leech List Unburnable Edit
// @namespace     https://www.wanikani.com
// @description   Shows top leeches on dashboard (replaces critical items) and all leeches on a dedicated page (replaces critical items)
// @author        ukebox (edited by Pep95)
// @version       1.2.8
// @require       https://code.jquery.com/jquery-3.3.1.min.js#sha256=FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=
// @include       https://www.wanikani.com/dashboard
// @include       https://www.wanikani.com/
// @include       https://www.wanikani.com/critical-items
// @grant         none
// @run-at        document-end
// ==/UserScript==

/*
jshint esversion: 6
*/

(function() {
    'use strict';

    let sumval;
    let dom = {};
    dom.$ = jQuery.noConflict(true);

    if (!window.wkof) {
        let response = confirm('WaniKani Dashboard Leech List script requires WaniKani Open Framework.\n Click "OK" to be forwarded to installation instructions.');

        if (response) {
            window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
        }

        return;
    }

    const leechThreshold = 1;
    const config = {
        wk_items: {
            options: {
                review_statistics: true,
                assignments: true
            },
            filter: {
                level: '1..+0',
                srs: '1..8'
            }
        }
    };

    window.wkof.include('Menu,Settings,ItemData');
    window.wkof.ready('Menu,Settings,ItemData').then(load_settings).then(install_menu).then(getItems).then(determineLeeches).then(updatePage);

    function getItems() {
        return window.wkof.ItemData.get_items(config);
    }

    function determineLeeches(items) {
        return items.filter(item => isLeech(item));
    }

    function summer( item ) {
        var sum = 0;
        console.log( "sum1: "+sum+" "+item);
        for( var impcnt in item ) {
            console.log( "sum1: "+sum+" what is item.impcnt: "+item.impcnt );
            if( item.hasOwnProperty( impcnt ) && item.impcnt == 1) {
                sum += parseFloat( item[impcnt] );
                console.log( "sum1: "+sum+" "+item );
            }
        }
        return sum;
    }

    function isLeech(item) {

        if (item.review_statistics === undefined) {
            return false;
        }

        let reviewStats = item.review_statistics;
        let meaningScore = computeLeechScore(reviewStats.meaning_incorrect, reviewStats.meaning_current_streak);
        let readingScore = computeLeechScore(reviewStats.reading_incorrect, reviewStats.reading_current_streak);

        item.leech_score = Math.max(meaningScore, readingScore);

        if (item.assignments.srs_stage == 9) {
            meaningScore = 0;
            readingScore = 0;
        }

        if (meaningScore < readingScore) {
            item.highestincorrect = reviewStats.reading_incorrect;
            item.highestincorrectstreak = reviewStats.reading_current_streak;
        } else {
            item.highestincorrect = reviewStats.meaning_incorrect;
            item.highestincorrectstreak = reviewStats.meaning_current_streak;
        }

        item.startleech = 9 - (item.assignments.srs_stage - item.highestincorrectstreak);

        switch(item.startleech) {
            case 8:
                if (item.highestincorrect>18) {
                    item.impossible = "不可能"
                    item.impcnt = 1
                } else {
                    item.impossible = " "
                    item.impcnt = 0
                }
                break;
            case 7:
                if (item.highestincorrect>14) {
                    item.impossible = "不可能"
                    item.impcnt = 1
                } else {
                    item.impossible = " "
                    item.impcnt = 0
                }
                break;
            case 6:
                if (item.highestincorrect>11) {
                    item.impossible = "不可能"
                    item.impcnt = 1
                } else {
                    item.impossible = " "
                    item.impcnt = 0
                }
                break;
            case 5:
                if (item.highestincorrect>7) {
                    item.impossible = "不可能"
                    item.impcnt = 1
                } else {
                    item.impossible = " "
                    item.impcnt = 0
                }
                break;
            case 4:
                if (item.highestincorrect>5) {
                    item.impossible = "不可能"
                    item.impcnt = 1
                } else {
                    item.impossible = " "
                    item.impcnt = 0
                }
                break;
            case 3:
                if (item.highestincorrect>2) {
                    item.impossible = "不可能"
                    item.impcnt = 1
                } else {
                    item.impossible = " "
                    item.impcnt = 0
                }
                break;
            case 2:
                if (item.highestincorrect>0) {
                    item.impossible = "不可能"
                    item.impcnt = 1
                } else {
                    item.impossible = " "
                    item.impcnt = 0
                }
                break;
            case 1:
                if (item.highestincorrect>0) {
                    item.impossible = "不可能"
                    item.impcnt = 1
                } else {
                    item.impossible = " "
                    item.impcnt = 0
                }
                break;
            default:
                item.impossible = " "
                item.impcnt = 0
        }

        switch(item.assignments.srs_stage) {
            case 8:
                item.srsstagename = "Enlightened";
                break;
            case 7:
                item.srsstagename = "Master";
                break;
            case 6:
                item.srsstagename = "Guru 2";
                break;
            case 5:
                item.srsstagename = "Guru 1";
                break;
            case 4:
                item.srsstagename = "Apprentice 4";
                break;
            case 3:
                item.srsstagename = "Apprentice 3";
                break;
            case 2:
                item.srsstagename = "Apprentice 2";
                break;
            case 1:
                item.srsstagename = "Apprentice 1";
                break;
            default:
                item.srsstagename = "Exception";
                break;
        }

        //var summed = summer( item );
        //console.log( "sum: "+summed );

        return meaningScore >= leechThreshold || readingScore >= leechThreshold;
    }

    function computeLeechScore(incorrect, currentStreak) {
        return incorrect / Math.pow((currentStreak || 0.5), 1.5);
    }


    function updatePage(items) {

        let is_dashboard = window.location.pathname !== "/critical-items";
        let totalleeches = items.length;

        if (is_dashboard) {
            items = items.sort((a, b) => (b.highestincorrect - a.highestincorrect)||(a.highestincorrectstreak - b.highestincorrectstreak)).slice(0,10);
        } else {
            items = items.sort((a, b) => (b.highestincorrect - a.highestincorrect)||(a.highestincorrectstreak - b.highestincorrectstreak));
        }

        console.log(items);

        makeLeechList(items, is_dashboard, totalleeches);
    }

    function round(number, decimals)
    {
        return +(Math.round(number + "e+" + decimals) + "e-" + decimals);
    }

    function makeLeechList(items, for_dashboard, totalleeches) {
        let rows = "";
        let textstuff = "";
        let sumval = 0;
        let temp = "";
        //writer = "";
        let d = new Date();

        //temp = "Leeches " + d.getdate() + "-" + d.getMonth()+1 + "-" + d.getFullYear + " " + d.getHours() + ":" + d.getMinutes() + ".txt";
        //textstuff = "You have " + totalleeches + " leeches as of " + d.getdate() + "-" + d.getMonth()+1 + "-" + d.getFullYear + " " + d.getHours() + ":" + d.getMinutes() + "\r\n\r\n";
        temp = "Leeches as of Now.txt";
        textstuff = "You have " + totalleeches + " leeches as of now.\r\n\r\n";

        //set fso = CreateObject("Scripting.FileSystemObject");
        //set s   = fso.CreateTextFile(temp, True);

        items.forEach(item => {
            let type = item.assignments.subject_type;
            //use slug by default (for kanji and vocab)
            let representation = item.data.slug;

            //The slug of a radical just has its name, we want the actual symbol.
            if (type === 'radical') {
                if (item.data.characters) {
                    //use characters for radicals when possible
                    representation = item.data.characters;
                } else if (item.data.character_images) {
                    //use SVG image for scalability
                    let image_data = item.data.character_images.find(x => x.content_type === "image/svg+xml" && x.metadata.inline_styles);
                    if (image_data) {
                        representation = `<img style="height: 1em; width: 1em; filter: invert(100%);" src="${image_data.url}" />`;
                    }
                }
            }

            if (for_dashboard) {
                rows+=`<tr class="${type}"><td><a href="${item.data.document_url}"><span lang="ja">${representation}</span><span class="pull-right"><table style="width:150px"><td width="40%" align="right"> ${item.impossible} </td><td width="30%" align="right">${item.highestincorrect}/${item.highestincorrectstreak}</td><td width="30%" align="right">${round(item.leech_score, 2)}</td></table> </span></a></td></tr>`;
            } else {
                rows+=`<tr class="${type}"><td><a href="${item.data.document_url}"><span lang="ja">${representation}</span><span class="pull-right"><table style="width:300px"><td width="25%" align="right"> ${item.impossible} </td><td width="5%" align="left"></td><td width="30%" align="left"> ${item.srsstagename} </td><td width="15%" align="right">${item.highestincorrect}/${item.highestincorrectstreak}</td><td width="15%" align="right">${round(item.leech_score, 2)}</td></table> </span></a></td></tr>`;
            }

            if (for_dashboard == 0 && wkof.settings.LeechListUnburnable.CreateTxt) {
                textstuff+=`${representation}\r\n`;
            }

            if (item.impcnt == 1) {
                sumval = sumval + 1;
            }

        });

        if (for_dashboard == 0 && wkof.settings.LeechListUnburnable.CreateTxt) {
            var a = document.createElement("a");
            a.href = "data:text," + textstuff;
            a.download = temp;
            a.click();
        }

        let sectionContent = `<h3 class="small-caps">${for_dashboard ? 'Top ' : ''}Leeches${for_dashboard ? ' (' + totalleeches + ')' : ''}</h3>
                              <table>
                                <tbody style="display: table-row-group;">
                                  ${rows}
                                </tbody>
                              </table>
                              <div class="see-more">
                                <a class="small-caps" ${for_dashboard ? 'href="critical-items"' : ''}>${for_dashboard ? 'See More Leeches...' : items.length + ' leeches total (' + sumval + ' burn-only leeches)'}</a>
                              </div>`;
        dom.$('.low-percentage').html(sectionContent);
    }

    // Load settings and set defaults
    function load_settings() {
        var defaults = {
            CreateTxt: 'false',
        };
        return wkof.Settings.load('LeechListUnburnable', defaults);
    }

    // Installs the options button in the menu
    function install_menu() {
        var config = {
            name: 'leech_list_unburnable',
            submenu: 'Settings',
            title: 'Leech List Unburnable Edit',
            on_click: open_settings
        };
        wkof.Menu.insert_script_link(config);
    }

    // Create the options
    function open_settings(items) {
        var config = {
            script_id: 'LeechListUnburnable',
            title: 'Leech List Unburnable Edit',
            content: {
                CreateTxt: {
                    type: 'checkbox',
                    label: 'Create Txt File',
                    hover_tip: 'Create Txt File when loading the Critical Items Page',
                    default: 'false'
                }
            }
        }
        var dialog = new wkof.Settings(config);
        dialog.open();
    }

    // Returns a promise and a resolve function
    function new_promise() {
        var resolve, promise = new Promise((res, rej)=>{resolve = res;});
        return [promise, resolve];
    }

})();