Greasy Fork is available in English.

Wanikani Multiple Answer Input (revamped)

Input multiple readings/meanings into Wanikani (revamped)

// ==UserScript==
// @name         Wanikani Multiple Answer Input (revamped)
// @namespace    buschwichtel
// @version      1.2.2
// @description  Input multiple readings/meanings into Wanikani (revamped)
// @author       Buschwichtel (original by Mempo)
// @match        https://www.wanikani.com/review/session
// @match        https://www.wanikani.com/lesson/session
// @match        https://www.wanikani.com/extra_study/session*
// @match        https://preview.wanikani.com/review/session
// @match        https://preview.wanikani.com/lesson/session
// @match        https://preview.wanikani.com/extra_study/session*
// @grant        none
// ==/UserScript==
/*jshint esversion: 6 */

const CONSOLE_PREFIX = "/// WKMAIR: ";
const SCRIPT_ID = "multiple_answer_input_rev";
const SCRIPT_NAME = "Multiple Answer Input (rev)";


(function() {
    'use strict';

    console.debug(CONSOLE_PREFIX + "INIT");

    setup();

    $('input#user-response').on('keydown',function(event){

        if (event.keyCode === 13){
            var isLesson = /^http.*:\/\/www\.wanikani\.com\/lesson/.test(location.href);
            var currentItem = $.jStorage.get(isLesson ? "l/currentQuizItem" : "currentItem");
            var questionType = $.jStorage.get(isLesson ? "l/questionType" : "questionType");

            if(!questionType){
                console.error(CONSOLE_PREFIX + "Invalid question, can't process.");
                return;
            }
            if(!currentItem){
                console.error(CONSOLE_PREFIX + "Invalid quiz item, can't process.");
                return;
            }

            // stops the event from bubbling up to the original wanikani script
            event.preventDefault();
            event.stopPropagation();

            var input = $('input#user-response')[0].value;
            var inputElements = input ? input.split(/[;]|[ ]{2,}/) : null;
            var wrongAnswers = [];
            var correctAnswers = [];

            if(inputElements){
                inputElements.forEach((element) => {
                    if(element !== "") {
                        var cleanedElement = cleanInput(element);

                        // this is wanikani's own result evaluation, so it should be identical to entering each input one at a time
                        var answer = answerChecker.evaluate(questionType, cleanedElement, currentItem);

                        console.debug(CONSOLE_PREFIX + "answerChecker evaluated: " + answer.passed + " for answer: " + cleanedElement);

                        if(answer.passed||answer.accurate){
                            correctAnswers.push(cleanedElement);
                        }
                        else {
                            wrongAnswers.push(cleanedElement);
                        }
                    }
                });

                var wrongAnswersString = "";
                var correctAnswersString = "";

                var anyWrongAnswers = wrongAnswers.length > 0;
                var anyCorrectAnswers = correctAnswers.length > 0;

                if(anyWrongAnswers) {
                    wrongAnswersString = wrongAnswers.join("; ");
                }
                if(anyCorrectAnswers) {
                    correctAnswersString = correctAnswers.join("; ");
                }

                console.info(CONSOLE_PREFIX + "Right answers given: " + (0 === correctAnswersString.length ? "none" : correctAnswersString) +
                             ", Wrong answers given: " + (0 === wrongAnswersString.length ? "none" : wrongAnswersString));

                if(wkof.settings.multiple_answer_input_rev.force_all_correct) {
                    if(anyWrongAnswers) {
                        // It doesn't matter how we send the answers, since they're wrong anyway.
                        // Note that any correct answers in the original input aren't part of the result here.
                        sendAnswer(wrongAnswersString);
                    }
                    else if(anyCorrectAnswers) {
                        // Since there were no wrong answer and at least one correct answer, all answers are correct.
                        // WaniKani only expects a single response, so we send only the first correct answer to wanikani, so it accepts it,
                        // then replace the result string with all of the answers again (since they are all correct)
                        sendAnswer(correctAnswers[0]);
                        $('input#user-response')[0].value = input;
                    }
                }
                else {
                    // If everything is wrong...
                    if(!anyCorrectAnswers) {
                        sendAnswer(wrongAnswersString);
                    }
                    // ...or everything is correct, we can just pass it off to the default
                    else if(!anyWrongAnswers && anyCorrectAnswers) {
                        sendAnswer(correctAnswers[0]);
                        $('input#user-response')[0].value = input;
                    }
                    else {
                        // put the input back into what the user actually entered
                        $('input#user-response')[0].value = input;

                        var msg = 'Not all of your answers were correct! Wrong: "' + wrongAnswersString + '"';

                        // show a message describing which answers were wrong & do the shaky thingy
                        if(!$("#answer-form form").is(":animated") && $('#user-response').val() !== '') {
                            $("#answer-form form").effect("shake", {}, 300, function() {
                                $("#additional-content").append($('<div id="answer-exception"><span>'+msg+'</span></div>').addClass("animated fadeInUp"));
                            });
                            $("#answer-form form").find("input").focus();
                        }
                    }
                }
            }
            else
            {
                // if we can't split it, just hand it off to the default script.
                // input is probably empty in this case, anyway.
                sendAnswer(input);
                console.log(CONSOLE_PREFIX + "No readable answers given.");
            }
        }
    });
})();

function setup() {
    if (!window.wkof) {
        alert(SCRIPT_NAME + ' requires Wanikani Open Framework.\nYou will now be forwarded to installation instructions.');
        window.location.href = 'https://community.wanikani.com/t/instructions-installing-wanikani-open-framework/28549';
        return;
    }

    wkof.include('Menu,Settings');
    wkof.ready('Menu,Settings').then(loadSettings);

    function loadSettings() {

        wkof.Menu.insert_script_link({name:SCRIPT_ID,submenu:'Settings',title:SCRIPT_NAME,on_click:openSettingsMenu});

        var defaults = {
            force_all_correct: true
        };
        wkof.Settings.load(SCRIPT_ID, defaults);
    }
}

function openSettingsMenu() {
    var dialog = new wkof.Settings({
        script_id: SCRIPT_ID,
        title: SCRIPT_NAME,
        content: {
            tabSettings: {type:'page', label:'Settings', content: {
                force_all_correct: {type:'checkbox', label:'Force all correct', default: true, hover_tip: 'When enabled, will fail you if a single answer given is incorrect.'}
            }}
        }
    });
    dialog.open();
}

function sendAnswer(answer) {
    $('input#user-response')[0].value = answer;
    $("#answer-form form button").click();
}

// Carbon copy of the first few lines of answerChecker.evaluate, sadly doesn't exist standalone :(
function cleanInput(input, questionType) {
    var normalizedResponse = normalizeResponse(input);
    if (questionType === "reading") {
        // normalizeReadingResponse DOES get exported, for some reason.
        normalizedResponse = answerChecker.normalizeReadingResponse(normalizedResponse);
    }
    return normalizedResponse;
}

// Carbon copy of answerChecker.normalizeResponse, but that doesn't get exported :(
function normalizeResponse(response) {
    return response
        .trim()
        .toLowerCase()
        .replace(/-/g, " ") // replace hyphen with space
        .replace(/\.|,|'|’|\/|:/g, ""); // replace periods, comma, colons, and apostrophe with blanks
}