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       2.1.0
// @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
            },
            filters: {
                srs: {value: '-1,0,9', invert: true}
            }
        }
    };

    const srs_stages = [
        'Initiate',
        'Apprentice 1',
        'Apprentice 2',
        'Apprentice 3',
        'Apprentice 4',
        'Guru 1',
        'Guru 2',
        'Master',
        'Enlightened',
        'Burned'
    ];

    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.mustBurn = (item.highestincorrect - item.highestincorrectstreak) + item.assignments.srs_stage >= 9;

        if (item.mustBurn){
            item.impossible = "不可能";
            item.impcnt = 1;
        } else {
            item.impossible = " ";
            item.impcnt = 0;
        }

        item.srsstagename = srs_stages[item.assignments.srs_stage];

        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+=`<li class="subject-character-grid__item">
                         <a class="subject-character subject-character--${type} subject-character--grid subject-character--unlocked title="${representation}" href="${item.data.document_url}">
                           <div class="subject-character__content">
                             <span class=subject-character__characters lang="ja">${representation}</span>
                             <div class="subject-character__info">
                               <span class="subject-character__additional-info">
                                 <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>
                             </div>
                           </div>
                         </a>
                       </li>`;
            } else {
                rows+=`<li class="subject-character-grid__item">
                         <a class="subject-character subject-character--${type} subject-character--grid subject-character--unlocked title="${representation}" href="${item.data.document_url}">
                           <div class="subject-character__content">
                             <span class=subject-character__characters lang="ja">${representation}</span>
                             <div class="subject-character__info">
                               <span class="subject-character__additional-info">
                                 <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>
                             </div>
                           </div>
                         </a>
                       </li>`;
            }

            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/plain;charset=utf-8," + encodeURIComponent(textstuff);
            a.download = temp;
            a.click();
        }

        if (for_dashboard == 1) {
          let sectionContent = `<div class="wk-panel__header">
                                  <h2 class="wk-panel__title">${for_dashboard ? 'Top ' : ''}Leeches${for_dashboard ? ' (' + totalleeches + ')' : ''}</h2>
                                </div>
                                <div class="wk-panel__content">
                                  <div class="dashboard-lists">
                                    <div class="subject-character-grid subject-character-grid--single-column">
                                      <ol class="subject-character-grid__items">
                                        ${rows}
                                      </ol>
                                    </div>
                                    <div class="dashboard-lists__buttons">
                                      <a class="wk-button wk-button--default" ${for_dashboard ? 'href="/critical-items"' : ''}>
                                        <span class="wk-button__text">
                                          ${for_dashboard ? 'See More Leeches...' : totalleeches + ' leeches total (' + sumval + ' burn-only leeches)'}
                                        </span>
                                      </a>
                                    </div>
                                  </div>
                                </div>`;
          dom.$('.wk-panel--dashboard-list-critical').html(sectionContent);
        } else {
          let sectionContent = `<ol class="subject-character-grid__items">
                                  ${rows}
                                  <li class="subject-character-grid__item">
                                    <a class="subject-character subject-character--grid subject-character--unlocked" style="background-color:#F4F4F4;">
                                      <div class="subject-character__content">
                                        <div class="subject-character__info" style="margin: 0 auto;">
                                          <span class="subject-character__additional-info">
                                            <div style="width: 100%; text-align: center; color: #333;">
                                              ${totalleeches} LEECHES TOTAL (${sumval} BURN-ONLY LEECHES)
                                            </div>
                                          </span>
                                        </div>
                                      </div>
                                    </a>
                                  </li>
                                </ol>`;
          dom.$('.subject-character-grid').html(sectionContent);
          dom.$('.page-header__title-text').html("Leech List");
          dom.$('.wk-text').html(totalleeches + " leeches in total (of which " + sumval + " burn-only leeches)");
          dom.$('.subject-character-grid').css('border-radius', '6px');
          dom.$('.subject-character-grid').css('overflow', 'hidden');
        }
    }

    // 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];
    }

})();

/*         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 );

/*                 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>`; */
/*                 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>`; */