arkandevel / Insta.Ling Bot

// ==UserScript==
// @name             Insta.Ling Bot
// @version          2022.06.03.01.r0.ge333da4
// @author           ArcybiskupstwoPolskie
// @description      Self-learning Insta.Ling bot in javascript.
// @match            https://instaling.pl/ling2/html_app/app.php*
// @namespace        net.arcybiskupstwopolskie.instaling-bot
// @license          MIT
// @updateURL        https://openuserjs.org/meta/arkandevel/Insta.Ling_Bot.meta.js
// @downloadURL      https://openuserjs.org/install/arkandevel/Insta.Ling_Bot.user.js
// ==/UserScript==
const inj = document.createElement("script");

inj.textContent = `
var localdb_template = {
    "version": 1,
    "users": {}
};

var dictionary_template = {
    "version": 2,
    "info": {
        "language_id": 0,
        "session_length": 0
    },
    "settings": {},
    "translations": {
        "byId": {},
        "byWord": {}
    }
};

storage_id = "net.arcybiskupstwopolskie.instaling-bot";

function translations_save(dictionary) {
    localdb = JSON.parse(localStorage.getItem(storage_id));
    localdb.users[currentChildId] = dictionary;
    localStorage.setItem(storage_id, JSON.stringify(localdb));
    console.info("Dictionary saved.");
}

function translations_load() {
    localdb = JSON.parse(localStorage.getItem(storage_id));
    if (localdb == null) {
        console.info("Initializing script database.");
        localdb = localdb_template;
        localStorage.setItem(storage_id, JSON.stringify(localdb_template));
    }
    result = localdb.users[currentChildId];
    if (result == null) {
        console.info("Initializing dictionary for child id: " + currentChildId);
        translations_save(dictionary_template);
        result = translations_load();
    }
    console.info("Dictionary loaded.");
    return result
}

function suspend_point_store(suspend_point, dictionary) {
    if (suspend_point != null) {
        dictionary.suspend_point = suspend_point;
    } else {
        delete dictionary.suspend_point;
    }
    translations_save(dictionary);
    return 0
}
// Load dictionary from storage api
var dictionary = translations_load();

// Sleep function
const sleep = (delay) => {return new Promise(resolve => setTimeout(resolve, delay))};

// Keeps how long a session will last
var max_session_length = dictionary["info"]["session_length"];
var current_session_length = 0;

// Stores if the script started with the session
var started_with_session = false;

// -- dict update
// Stores if dictionary has been updated
var dictionary_updated = false;

function dictionary_updated_set() {
    if (!dictionary_updated) {
        dictionary_updated = true;
        console.log("The dictionary has changed.");
    }
}

function dictionary_updated_unset() {
    if (dictionary_updated) {
        dictionary_updated = false;
        console.log("The dictionary has been saved.");
    }
}
// -- done

function emergency_save() {
    translations_save(dictionary);
}

// --
var bot_same_language = false;
var bot_same_language_checked = false;

function bot_same_language_check() {
    if (!bot_same_language_checked) {
        if (dictionary["info"]["language_id"] == 0) {
            console.log("Language id not set.");
            dictionary["info"]["language_id"] = currentLanguageId;
            dictionary_updated_set();
        }
        // Check if dictionary language is the same as page
        bot_same_language = currentLanguageId == dictionary["info"]["language_id"];
        bot_same_language_checked = true;
    }
}
// -- done

// -- I'm bad at naming things.
function bot_bug_log(message) {
    console.error(message + "\\nPlease report this to the maintainer.");
}
// -- done

// -- Suspend / Resume functionality
function suspend_point_create() {
    console.log("Creating suspend point");
    suspend_point = {
        version: 1,
        started_with_session: started_with_session,
        current_session_length: current_session_length
    };
    console.debug(suspend_point);
    dictionary.suspend_point = suspend_point;
    suspend_point_store(suspend_point, dictionary);
    console.info("Suspend point created.");
}

function suspend_point_load() {
    if (dictionary.suspend_point != undefined) {
        suspend_point = dictionary.suspend_point;
        console.debug(dictionary.suspend_point);
        started_with_session = suspend_point.started_with_session;
        current_session_length = suspend_point.current_session_length;
        console.info("Suspend point loaded.");
        return 0;
    } else {
        console.log("No suspend point found.");
        return 1;
    }
}

function suspend_point_delete() {
    if (dictionary.suspend_point != undefined) {
        suspend_point_store(null, dictionary);
        console.info("Suspend point deleted.");
        return 0;
    } else {
        console.log("No suspend point found.");
        return 1;
    }
}
// -- done

// --
/*
 *  0 for visible
 *  1 for invisible
 * -1 for unknown
 */
function check_page_visibility(page_name) {
    let page = document.getElementById("allpage").children[page_name];
    switch (page.style["display"]) {
        case "block":
            return 0
        case "none":
            return 1
        default:
            bot_bug_log(\`Page "\${page_name}" has an unknown display status: "\${start_page.style}"\`);
            return -1
    }
}
// --

// -- Simple translator
function bot_translate(id, word) {
    bot_same_language_check();
    if (bot_same_language) {
        var result = dictionary.translations.byId[id];
        if (result != undefined) {
            console.log('✔️ Word id "' + id+ '" means "' + result + '".');
            return result
        } else {
            console.log('❌ Word id "' + id + '" was not found in the blob.');
        }
    }
    var result = dictionary.translations.byWord[word];
    if (result != undefined) {
        console.log('✔️ "' + word + '" means "' + result + '".');
        return result
    } else {
        console.log('❌ "' + word + '" was not found in the blob.');
        return ""
    }
}
function bot_autofill(id, translation) {
    textbox.insertIntoTextArea(bot_translate(id, translation));
}
// -- done

// -- Blob appending
function dictionary_add(id, word, translation) {
    bot_same_language_check();
    // Adding to id list
    if (bot_same_language && dictionary.translations.byId[id] != word) {
        console.log('Adding translation from word id "' + id + '" to "' + word + '".');
        dictionary.translations.byId[id] = word;
        dictionary_updated_set();
        // Adding to word list
        if (dictionary.translations.byWord[translation] != word) {
            console.log('Adding translation from "' + translation + '" to "' + word + '".');
            dictionary.translations.byWord[translation] = word;
        }
    }
    if (started_with_session) {
        console.log(\`Words left: \${max_session_length - current_session_length}\`);
    }
}
// -- done

// -- getNextWord_hook
function getNextWord_hook(data) {
    if (data.maxWords != undefined) {
        if (max_session_length != data.maxWords) {
            console.log(\`The session length reported by InstaLing (\${data.maxWords}) is not the same as reported by the dictionary (\${max_session_length})\`);
            max_session_length = data.maxWords;
            dictionary.info.session_length = max_session_length;
            dictionary_updated_set();
        }
    }
    switch (data.type) {
        case "marketing":
            console.log("I hate ads.");
            break;
        case "word":
            bot_autofill(data.id, data.translations);
            break;
        default:
            bot_bug_log("Insta.ling has sent an unknown data type.");
            console.log("Trying to autofill anyway.");
            bot_autofill(data.id, data.translations);
    }
    enable_paste();
}

function getNextWord_fail_hook() {
    console.error("Failed to get next word!");
    suspend_point_create();
}

// -- Hook into getNextWord request
function getNextWordRepeat() {
    // - Untouched
    \$.ajax({
        url: '../server/actions/generate_next_word.php',
        type: "POST",
        dataType: 'json',
        data: {child_id:currentChildId, date:new Date().getTime() , repeat:repeat  , start:start , end:end }
    }).done(function(data) {
        if (typeof data.id == 'undefined') {
            finishRepeatPageShow(data.summary);
        } else{
            learningPageShow(data.id, data.speech_part, data.usage_example, data.translations, data.word, data.has_audio, data.audio_file_name, data.is_new_word, data.type == 'marketing');
            // -- done
            getNextWord_hook(data);
            // - Untouched
        }
    }).error(function() {
        getNextWord_fail_hook();
        alert('Błąd połączenia');
    });
    // -- done
}
function getNextWord() {
    // - Untouched
    \$.ajax({
        url: '../server/actions/generate_next_word.php',
        type: "POST",
        dataType: 'json',
        data: {child_id:currentChildId, date:new Date().getTime()}
    }).done(function(data) {
        if (typeof data.id == 'undefined') {
            finishPageShow(data.summary);
        } else{
            learningPageShow(data.id, data.speech_part, data.usage_example, data.translations, data.word, data.has_audio, data.audio_file_name, data.is_new_word, data.type == 'marketing');
            // -- done
            getNextWord_hook(data);
            // - Untouched
        }
    }).error(function() {
        getNextWord_fail_hook();
        alert('Błąd połączenia');
    });
    // -- done
}
// -- done

// -- Hook into updateParams
function updateParams_fail_hook() {
    console.error("Failed to send word!");
    suspend_point_create();
}

// -- Self-learning
function updateParams(id, answer, show_grade, version) {
    // - Untouched
    \$.ajax({
        url: '../server/actions/save_answer.php',
        type: "POST",
        dataType: 'json',
        data: {child_id:currentChildId, word_id: id, answer:answer, version:version}
    }).done(function(data) {
        // - done
        // Adds word (data.translations) to the database with it's translation (data.word)
        if (data.grade === 1) {
            current_session_length++;
        } else if (data.grade === 0) {
            // Do nothing
        } else {
            bot_bug_log("Insta.ling has sent an unknown grade.");
        }
        dictionary_add(data.id, data.word, data.translations);
        // - Untouched
        showAnswerPage(id, answer, data.usage_example, data.translations, data.grade, data.word,
            data.answershow, data.has_audio, show_grade);
    }).error(function() {
        updateParams_fail_hook();
        alert('Błąd połączenia');
    });
    // - done
}
// -- done

// -- Blob output
function finishPageShow(summary) {
    // - Untouched
    \$('#answer').off('keyup');
    \$('body').off('keyup');
    \$('#return_mainpage').off('click');
    \$("body").keyup(function(e){
        if (e.keyCode == 13) {
            \$('body').off('keyup');
            \$('.back').trigger('click');
        }
    });
    \$('#return_mainpage').click(function (){
        \$('body').off('keyup');
        \$('.back').trigger('click');
    });
    \$('#session_result').html(nl2br(summary));
    \$('#grade_report_button').click(getGradeReport);
    \$('#loading').hide();
    \$('.back').show();
    \$('#finish_page').show();
    // - done

    suspend_point_delete();
    translations_save(dictionary);
    if (!dictionary_updated) {
        console.log("The word databse did not change.");
    }
}
// -- done

function beforeunload_hook(event) {
    if (!dictionary.settings.onbeforereload_suspend_point_disabled) {
        if (
            check_page_visibility("start_session_page") == 1 &&
            check_page_visibility("finish_page") == 1
        ) {
            suspend_point_create();
        } else {
            console.info("Skipping suspend point creation.");
        }
    }
    if (dictionary_updated) {
        translations_save(dictionary);
    }
    return;
}

// --
async function session_start_check() {
    let exit_code = suspend_point_load();
    console.info("Checking if the start page is visible.");
    let page_visible = false;
    for (let i = 0; i < 100; i++) {
        if (check_page_visibility("start_session_page") == 0) {
            console.log("Started with session.");
            page_visible = true;
            break;
        } else {
            if ((i + 1) % 50 == 0) {
                console.log(\`Tried \${i + 1} times.\`)
            }
            await sleep(0.5 * 100);
        }
    }
    if (page_visible) {
        if (exit_code === 0) {
            console.warn("The session saved in the suspend point seems to be finished.");
            suspend_point_delete();
        }
        started_with_session = true;
        current_session_length = 0;
    } else {
        if (exit_code === 0) {
            console.log("Continuing from suspend point.");
            console.log(\`Words left: \${max_session_length - current_session_length}\`);
        } else {
            console.warn("The script did not start with the session.");
        }
    }
}

function enable_paste() {
    textbox = \$('input[id=answer]');
    textbox.off("paste");
    console.info("Copy-paste enabled.");
}

function delete_audio() {
    \$("#jquery_audioPlayer")[0].remove();
    console.info("Audio player deleted.");
}

function beforeunload_setup() {
    console.debug("Adding event listener to beforeunload");
    addEventListener("beforeunload", beforeunload_hook);
}
// -- done

// -- Main function
async function main() {
    let _ = session_start_check();
    enable_paste();
    if (dictionary.settings != undefined) {
        if (!dictionary.settings.sound_enable) {
            delete_audio();
        }
    } else {
        console.log("No settings found in dictionary.");
        dictionary.settings = {};
        dictionary_updated_set();
        delete_audio();
    }
    await _; delete _;
    beforeunload_setup();
    console.info("Script ready!");
}
// -- done

// Run main
main();
`;

document.body.appendChild(inj);