/* ---------------------------------------------------------------------------- Online Moodle/Elearning/KMOOC test help Greasyfork: GitLab: This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ------------------------------------------------------------------------- */ // ==UserScript== // @name Moodle/Elearning/KMOOC test help // @version 2.0.1.13 // @description Online Moodle/Elearning/KMOOC test help // @author MrFry // @match https://elearning.uni-obuda.hu/main/* // @match https://elearning.uni-obuda.hu/kmooc/* // @match https://mooc.unideb.hu/* // @match https://itc.semmelweis.hu/moodle/* // @match https://qmining.frylabs.net/* // @match http://qmining.frylabs.net/* // @noframes // @run-at document-start // @grant GM_getResourceText // @grant GM_info // @grant GM_getValue // @grant GM_setValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_openInTab // @grant unsafeWindow // @license GNU General Public License v3.0 or later // @supportURL qmining.frylabs.net // @contributionURL qmining.frylabs.net // @namespace https://qmining.frylabs.net // @updateURL https://qmining.frylabs.net/moodle-test-userscript/stable.user.js?up // ==/UserScript== // // TODO: // grabboxes test on quiz page (function() { // eslint-disable-line // GM functions, only to disable ESLINT errors /* eslint-disable */ const a = Main; const usf = unsafeWindow; function getVal(name) { return GM_getValue(name); } function setVal(name, val) { return GM_setValue(name, val); } function delVal(name) { return GM_deleteValue(name); } function openInTab(address, options) { GM_openInTab(address, options); } function xmlhttpRequest(opts) { GM_xmlhttpRequest(opts); } function info() { return GM_info; } /* eslint-enable */ var addEventListener; // add event listener function let serverAdress = "https://qmining.frylabs.net/"; let apiAdress = "https://api.frylabs.net/"; const ircAddress = "https://kiwiirc.com/nextclient/irc.sub.fm/#qmining"; // forcing pages for testing. unless you test, do not set these to true! // only one of these should be true for testing const forceTestPage = false; const forceResultPage = false; const forceDefaultPage = false; const logElementGetting = false; const log = true; const motdShowCount = 3; /* Ammount of times to show motd */ let infoExpireTime = 60; // Every n seconds basic info should be loaded from server var motd = ""; var lastestVersion = ""; var subjInfo; if (getVal("ISDEVEL")) { infoExpireTime = 1; serverAdress = "http://localhost:8080/"; apiAdress = "http://localhost:80/"; } const huTexts = { lastChangeLog: "", fatalError: "Fatál error. Check console (f12). Kattints az üzenetre az összes kérdés/válaszért manuális kereséshez!", consoleErrorInfo: "Itteni hibák 100% a moodle hiba. Kivéve, ha oda van írva hogy script error ;) Ha ilyesmi szerepel itt, akkor olvasd el a segítség szekciót!", freshStartWarning: '

Moodle teszt userscript:

1.5.0 verzió: a script mostantól XMLHTTP kéréseket küld szerver fele! Erre a userscript futtató kiegészítőd is figyelmeztetni fog! Ha ez történik, a script rendes működése érdekében engedélyezd (Always allow domain)! Ha nem akarod, hogy ez történjen, akkor ne engedélyezd, vagy a menüben válaszd ki a "helyi fájl használata" opciót!

Elküldött adatok: minden teszt után a kérdés-válasz páros. Fogadott adatok: Az összes eddig ismert kérdés. Érdemes help-et elolvasni!!!

Ez az ablak frissités után eltűnik. Ha nem, akkor a visza gombbal próbálkozz.
', noResult: "Nincs találat :( Kattints az üzenetre az összes kérdés/válaszért manuális kereséshez!", videoHelp: "Miután elindítottad: Play/pause: space. Seek: Bal/jobb nyíl.", menuButtonText: "Kérdések Menu", couldntLoadDataPopupMenuText: "A kérdéseket nem lehetett beolvasni, ellenőrizd hogy elérhető-e a szerver", showGreetingOnEveryPage: "Üdvözlő üzenet mutatása minden oldalon", close: "Bezárás", help: "Help", websiteBugreport: "Weboldal / Bug report", contribute: "Contribute", donate: "Donate", retry: "Újrapróbálás", ircButton: "IRC", invalidPW: "Hibás jelszó: ", search: "Keresés ...", loading: "Betöltés ...", login: "Belépés", requestPWInsteadOfLogin: "Jelszó igénylés", contributeTitle: "Hozzájárulás a script és weboldal fejleszétéshez", newPWTitle: "Új jelszó új felhasználónak", pwRequest: "Új jelszó", noServer: "Nem elérhető a szerver!", noUser: "Nem vagy bejelentkezve!", noServerConsoleMessage: `Nem elérhető a szerver, vagy kis eséllyel kezeletlen hiba történt! Ha elérhető a weboldal, akkor ott meg bírod nézni a kérdéseket itt: ${serverAdress}legacy`, }; var texts = huTexts; // : question-classes {{{ const specialChars = ["&", "\\+"]; const assert = val => { if (!val) { throw new Error("Assertion failed"); } }; class StringUtils { RemoveStuff(value, removableStrings, toReplace) { removableStrings.forEach(x => { var regex = new RegExp(x, "g"); value = value.replace(regex, toReplace || ""); }); return value; } SimplifyQuery(q) { assert(q); var result = q.replace(/\n/g, " ").replace(/\s/g, " "); return this.RemoveUnnecesarySpaces(result); } ShortenString(toShorten, ammount) { assert(toShorten); var result = ""; var i = 0; while (i < toShorten.length && i < ammount) { result += toShorten[i]; i++; } return result; } ReplaceCharsWithSpace(val, char) { assert(val); assert(char); var toremove = this.NormalizeSpaces(val); var regex = new RegExp(char, "g"); toremove = toremove.replace(regex, " "); return this.RemoveUnnecesarySpaces(toremove); } // removes whitespace from begining and and, and replaces multiple spaces with one space RemoveUnnecesarySpaces(toremove) { assert(toremove); toremove = this.NormalizeSpaces(toremove); while (toremove.includes(" ")) { toremove = toremove.replace(/ {2}/g, " "); } return toremove.trim(); } RemoveSpecialChars(value) { assert(value); return this.RemoveStuff(value, specialChars, " "); } // if the value is empty, or whitespace EmptyOrWhiteSpace(value) { // replaces /n-s with "". then replaces spaces with "". if it equals "", then its empty, or only consists of white space if (value === undefined) { return true; } return ( value .replace(/\n/g, "") .replace(/ /g, "") .replace(/\s/g, " ") === "" ); } // damn nonbreaking space NormalizeSpaces(input) { assert(input); return input.replace(/\s/g, " "); } SimplifyStack(stack) { return this.SimplifyQuery(stack); } } const SUtils = new StringUtils(); // : }}} // : DOM getting stuff {{{ // all dom getting stuff are in this sections, so on // moodle dom change, stuff breaks here //Stealth by An0 with love function StealthOverlay() { //call this before the document scripts const document = window.document; const neverEqualPlaceholder = Symbol(`never equal`); //block probing for undefined values in the hooks let shadowRootHost = neverEqualPlaceholder; let shadowRootNewHost = neverEqualPlaceholder; const apply = Reflect.apply; //save some things in case they get hooked (only for unsafe contexts) if (usf.Error.hasOwnProperty("stackTraceLimit")) { Reflect.defineProperty(usf.Error, "stackTraceLimit", { value: undefined, writable: false, enumerable: false, configurable: false, }); } const shadowGetHandler = { apply: (target, thisArg, argumentsList) => apply( target, thisArg === shadowRootHost ? shadowRootNewHost : thisArg, argumentsList ), }; const original_attachShadow = usf.Element.prototype.attachShadow; const attachShadowProxy = new Proxy( original_attachShadow, shadowGetHandler ); usf.Element.prototype.attachShadow = attachShadowProxy; const getShadowRootProxy = new Proxy( Object.getOwnPropertyDescriptor( usf.Element.prototype, "shadowRoot" ).get, shadowGetHandler ); Object.defineProperty(usf.Element.prototype, "shadowRoot", { get: getShadowRootProxy, }); const getHostHandler = { apply: function() { let result = apply(...arguments); return result === shadowRootNewHost ? shadowRootHost : result; }, }; const getHostProxy = new Proxy( Object.getOwnPropertyDescriptor( usf.ShadowRoot.prototype, "host" ).get, getHostHandler ); Object.defineProperty(usf.ShadowRoot.prototype, "host", { get: getHostProxy, }); const shadowRootSetInnerHtml = Object.getOwnPropertyDescriptor( ShadowRoot.prototype, "innerHTML" ).set; const documentFragmentGetChildren = Object.getOwnPropertyDescriptor( DocumentFragment.prototype, "children" ).get; const documentGetBody = Object.getOwnPropertyDescriptor( Document.prototype, "body" ).get; const nodeAppendChild = Node.prototype.appendChild; const overlay = document.createElement("div"); overlay.style.cssText = "position:absolute;left:0;top:0"; const addOverlay = () => { shadowRootHost = apply(documentGetBody, document, []); const shadowRoot = apply(original_attachShadow, shadowRootHost, [ { mode: "closed" }, ]); apply(shadowRootSetInnerHtml, shadowRoot, [ `
`, ]); shadowRootNewHost = apply( documentFragmentGetChildren, shadowRoot, [] )[0]; apply(nodeAppendChild, shadowRoot, [overlay]); }; if (!document.body) { document.addEventListener("DOMContentLoaded", addOverlay); } else { addOverlay(); } return overlay; } const overlay = StealthOverlay(); function createHoverOver(target) { const overlayElement = document.createElement("div"); overlayElement.style.cssText = "position:fixed; pointer-events: none; user-select: none; z-index:10000"; overlay.append(overlayElement); let currX, currY, currWidth, currHeight; const copyBoundingRect = () => { let { x, y, width, height } = target.getBoundingClientRect(); if (x !== currX) { overlayElement.style.left = x + "px"; currX = x; } if (y !== currY) { overlayElement.style.top = y + "px"; currY = y; } if (width !== currWidth) { overlayElement.style.width = width + "px"; currWidth = width; } if (height !== currHeight) { overlayElement.style.height = height + "px"; currHeight = height; } }; copyBoundingRect(); const interval = setInterval(copyBoundingRect, 30); overlayElement.destroy = () => { clearInterval(interval); overlayElement.remove(); }; return overlayElement; } class QuestionsPageModell { GetAllQuestionsDropdown() { if (logElementGetting) { Log("getting dropdown question"); } let items = document .getElementById("responseform") .getElementsByTagName("p")[0].childNodes; let r = ""; items.forEach(item => { if (item.tagName === undefined) { r += item.nodeValue; } }); return r; } GetAllQuestionsQtext() { if (logElementGetting) { Log("getting all questions qtext"); } return document .getElementById("responseform") .getElementsByClassName("qtext"); // getting questions } GetAllQuestionsP() { if (logElementGetting) { Log("getting all questions by tag p"); } return document .getElementById("responseform") .getElementsByTagName("p"); } GetFormulationClearfix() { if (logElementGetting) { Log("getting formulation clearfix lol"); } return document.getElementsByClassName("formulation clearfix"); } GetAnswerOptions() { if (logElementGetting) { Log("getting all answer options"); } return this.GetFormulationClearfix()[0].childNodes[3].innerText; } GetQuestionImages() { if (logElementGetting) { Log("getting question images"); } return this.GetFormulationClearfix()[0].getElementsByTagName("img"); } // this function should return the question, posible answers, and image names GetQuestionFromTest() { var questions; // the important questions var allQuestions; // all questions try { allQuestions = this.GetAllQuestionsQtext(); // getting questions if (allQuestions.length === 0) { var ddq = this.GetAllQuestionsDropdown(); if (SUtils.EmptyOrWhiteSpace(ddq)) { var questionData = ""; for (var j = 0; j < allQuestions.length; j++) { let subAllQuestions = allQuestions[j].childNodes; for (let i = 0; i < subAllQuestions.length; i++) { if ( subAllQuestions[i].data !== undefined && !SUtils.EmptyOrWhiteSpace( subAllQuestions[i].data ) ) { questionData += subAllQuestions[i].data + " "; // adding text to question data } } } questions = [questionData]; } else { questions = [ddq]; } } else { questions = []; for (let i = 0; i < allQuestions.length; i++) { questions.push(allQuestions[i].innerText); } } } catch (e) { Exception(e, "script error at getting question:"); } var imgNodes = ""; // the image nodes for questions try { imgNodes = this.GetQuestionImages(); // getting question images, if there is any AddImageNamesToImages(imgNodes); // adding image names to images, so its easier to search for, or even guessing } catch (e) { Log(e); Log("Some error with images"); } questions = questions.map(item => { if (item) { return SUtils.ReplaceCharsWithSpace(item, "\n"); } }); return { imgnodes: imgNodes, allQ: allQuestions, q: questions, }; } } class ResultsPageModell { GetFormulationClearfix() { if (logElementGetting) { Log("getting formulation clearfix lol"); } return document.getElementsByClassName("formulation clearfix"); } GetGrade(i) { if (logElementGetting) { Log("getting grade"); } const fcf = QPM.GetFormulationClearfix()[i]; return fcf.parentNode.parentNode.childNodes[0].childNodes[2] .innerText; } DetermineQuestionType(nodes) { let qtype = ""; let i = 0; while (i < nodes.length && qtype === "") { let inps = nodes[i].getElementsByTagName("input"); if (inps.length > 0) { qtype = inps[0].type; } i++; } return qtype; } GetSelectAnswer(i) { if (logElementGetting) { Log("getting selected answer"); } var t = document.getElementsByTagName("select"); if (t.length > 0) { return t[i].options[t[i].selectedIndex].innerText; } } GetCurrQuestion(i) { if (logElementGetting) { Log("getting curr questions by index: " + i); } return document.getElementsByTagName("form")[0].childNodes[0] .childNodes[i].childNodes[1].childNodes[0].innerText; } GetFormResult() { if (logElementGetting) { Log("getting form result"); } var t = document.getElementsByTagName("form")[0].childNodes[0] .childNodes; if (t.length > 0 && t[0].tagName === undefined) { // debreceni moodle return document.getElementsByTagName("form")[1].childNodes[0] .childNodes; } else { return t; } } GetAnswerNode(i) { if (logElementGetting) { Log("getting answer node"); } var results = this.GetFormResult(); // getting results element var r = results[i].getElementsByClassName("answer")[0].childNodes; var ret = []; for (var j = 0; j < r.length; j++) { if ( r[j].tagName !== undefined && r[j].tagName.toLowerCase() === "div" ) { ret.push(r[j]); } } let qtype = this.DetermineQuestionType(ret); return { nodes: ret, type: qtype, }; } GetCurrentAnswer(i) { if (logElementGetting) { Log("getting curr answer by index: " + i); } var results = this.GetFormResult(); // getting results element var t = results[i] .getElementsByClassName("formulation clearfix")[0] .getElementsByTagName("span"); if (t.length > 2) { return t[1].innerHTML.split("
")[1]; } } GetQText(i) { if (logElementGetting) { Log("getting qtext by index: " + i); } var results = this.GetFormResult(); // getting results element return results[i].getElementsByClassName("qtext"); } GetDropboxes(i) { if (logElementGetting) { Log("getting dropboxes by index: " + i); } var results = this.GetFormResult(); // getting results element return results[i].getElementsByTagName("select"); } GetAllAnswer(index) { if (logElementGetting) { Log("getting all answers, ind: " + index); } return document.getElementsByClassName("answer")[index].childNodes; } GetPossibleAnswers(i) { if (logElementGetting) { Log("getting possible answers"); } var results = this.GetFormResult(); // getting results element var items = results[i].getElementsByTagName("label"); var r = []; for (var j = 0; j < items.length; j++) { const TryGetCorrect = j => { var cn = items[j].parentNode.className; if (cn.includes("correct")) { return ( cn.includes("correct") && !cn.includes("incorrect") ); } }; r.push({ value: items[j].innerText, iscorrect: TryGetCorrect(j), }); } return r; } GetAnswersFromGrabBox(i) { try { if (logElementGetting) { Log("testing if question is grab-box"); } let results = this.GetFormResult(); // getting results element let t = results[i].getElementsByClassName("dragitems")[0] .childNodes; if (t.length !== 1) { Log("grab box drag items group length is not 1!"); Log(results[i].getElementsByClassName("dragitems")[0]); } let placedItems = t[0].getElementsByClassName("placed"); let res = []; for (let i = 0; i < placedItems.length; i++) { let item = placedItems[i]; res.push({ text: item.innerText, left: item.style.left, top: item.style.top, }); } return res; } catch (e) { console.info(e); } } GetRightAnswerIfCorrectShown(i) { if (logElementGetting) { Log("getting right answer if correct shown"); } var results = this.GetFormResult(); // getting results element return results[i].getElementsByClassName("rightanswer"); } GetWrongAnswerIfCorrectNotShown(i) { if (logElementGetting) { Log("getting wrong answer if correct not shown"); } var results = this.GetFormResult(); // getting results element var n = results[i].getElementsByTagName("i")[0].parentNode; if (n.className.includes("incorrect")) { return results[i].getElementsByTagName("i")[0].parentNode .innerText; } else { return ""; } } GetRightAnswerIfCorrectNotShown(i) { if (logElementGetting) { Log("Getting right answer if correct not shown"); } var results = this.GetFormResult(); // getting results element var n = results[i].getElementsByTagName("i")[0].parentNode; if ( n.className.includes("correct") && !n.className.includes("incorrect") ) { return results[i].getElementsByTagName("i")[0].parentNode .innerText; } } GetFormCFOfResult(result) { if (logElementGetting) { Log("getting formulation clearfix"); } return result.getElementsByClassName("formulation clearfix")[0]; } GetResultText(i) { if (logElementGetting) { Log("getting result text"); } var results = this.GetFormResult(); // getting results element return this.GetFormCFOfResult(results[i]).getElementsByTagName("p"); } GetResultImage(i) { if (logElementGetting) { Log("getting result image"); } var results = this.GetFormResult(); // getting results element return this.GetFormCFOfResult(results[i]).getElementsByTagName( "img" ); } // gets the question from the result page // i is the index of the question GetQuestionFromResult(i) { var temp = this.GetQText(i); var currQuestion = ""; if (temp.length > 0) { currQuestion = temp[0].innerText; // adding the question to curr question as .q } else { // this is black magic fuckery a bit if (this.GetDropboxes(i).length > 0) { var allNodes = this.GetResultText(i); currQuestion = ""; for (var k = 0; k < allNodes.length; k++) { var allQuestions = this.GetResultText(i)[k].childNodes; for (var j = 0; j < allQuestions.length; j++) { if ( allQuestions[j].data !== undefined && !SUtils.EmptyOrWhiteSpace(allQuestions[j].data) ) { currQuestion += allQuestions[j].data + " "; } } } } else { try { currQuestion = this.GetCurrQuestion(i); } catch (e) { currQuestion = "REEEEEEEEEEEEEEEEEEEEE"; // this shouldnt really happen sry guys Log("Unable to get question in GetQuestionFromResult"); } } } return currQuestion; } // tries to get right answer from result page // i is the index of the question GetRightAnswerFromResult(i) { var fun = []; // "húzza oda ..." skip fun.push(i => { let temp = RPM.GetAnswersFromGrabBox(i); return temp .map(x => { return x.text; }) .join(", "); }); // the basic type of getting answers fun.push(i => { var temp = RPM.GetRightAnswerIfCorrectShown(i); // getting risht answer if (temp.length > 0) { return temp[0].innerText; } // adding the answer to curr question as .a }); // if there is dropdown list in the current question fun.push(i => { if (RPM.GetDropboxes(i).length > 0) { return RPM.GetCurrentAnswer(i); } }); // if the correct answers are not shown, and the selected answer // is correct fun.push(i => { return RPM.GetRightAnswerIfCorrectNotShown(i); }); // if there is dropbox in the question fun.push(i => { return RPM.GetSelectAnswer(i); }); // if the correct answers are not shown, and the selected answer // is incorrect, and there are only 2 options fun.push(i => { var possibleAnswers = RPM.GetPossibleAnswers(i); if (possibleAnswers.length === 2) { for (var k = 0; k < possibleAnswers.length; k++) { if (possibleAnswers[k].iscorrect === undefined) { return possibleAnswers[k].value; } } } }); // if everything fails fun.push(i => { return undefined; }); var j = 0; var currAnswer; while (j < fun.length && SUtils.EmptyOrWhiteSpace(currAnswer)) { try { currAnswer = fun[j](i); } catch (e) { console.info(e); } j++; } return currAnswer; } GuessCorrectIn2LengthAnswersByIncorrect(items) { const first = items[0]; const second = items[1]; if (first.className.includes("incorrect")) { return second.innerText; } if (second.className.includes("incorrect")) { return first.innerText; } } GuessCorrectIn2LengthAnswersByPoints(i, items) { const first = { elem: items[0], val: items[0].childNodes[0].checked, text: items[0].innerText, }; const second = { elem: items[1], val: items[1].childNodes[0].checked, text: items[1].innerText, }; const grade = RPM.GetGrade(i); // 1,00 közül 1,00 leosztályozva const grades = grade.split(" ").reduce((acc, text) => { if (text.includes(",")) { // FIXME: fancy regexp acc.push(parseInt(text)); } else if (text.includes(".")) { // FIXME: fancy regexp acc.push(parseInt(text)); } return acc; }, []); if (grades[0] === 1) { if (first.val) { return first.text; } else { return second.text; } } else { if (!first.val) { return first.text; } else { return second.text; } } } // version 2 of getting right answer from result page // i is the index of the question GetRightAnswerFromResultv2(i) { try { var answerNodes = this.GetAnswerNode(i); let items = answerNodes.nodes; if (answerNodes.type === "checkbox") { return RPM.GetRightAnswerFromResult(i); } for (let j = 0; j < items.length; j++) { let cn = items[j].className; if (cn.includes("correct") && !cn.includes("incorrect")) { return items[j].getElementsByTagName("label")[0] .innerText; } } if (items.length === 2) { const resByIncorrect = this.GuessCorrectIn2LengthAnswersByIncorrect( items ); if (!resByIncorrect) { const resPoints = this.GuessCorrectIn2LengthAnswersByPoints( i, items ); return resPoints; } return resByIncorrect; } } catch (e) { Log("error at new nodegetting, trying the oldschool way"); } } } class MiscPageModell { GetCurrentSubjectName() { if (logElementGetting) { Log("getting current subjects name"); } return ( document .getElementById("page-header") .innerText.split("\n")[0] || "" ); } GetVideo() { if (logElementGetting) { Log("getting video stuff"); } return document.getElementsByTagName("video")[0]; } GetVideoElement() { if (logElementGetting) { Log("getting video element"); } return document.getElementById("videoElement").parentNode; } GetInputType(answers, i) { if (logElementGetting) { Log("getting input type"); } return answers[i].getElementsByTagName("input")[0].type; } } var QPM = new QuestionsPageModell(); var RPM = new ResultsPageModell(); var MPM = new MiscPageModell(); // : }}} // : Main function {{{ let timerStarted = false; Main(); function Main() { "use strict"; console.log("Moodle / E-Learning script"); console.time("main"); timerStarted = true; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", Init); } else { Init(); } } function AfterLoad() { const url = location.href; // eslint-disable-line try { if ( (url.includes("/quiz/") && url.includes("attempt.php")) || forceTestPage ) { // if the current page is a test HandleQuiz(); } else if ( (url.includes("/quiz/") && url.includes("review.php")) || forceResultPage ) { // if the current window is a test-s result HandleResults(url); } else if ( (!url.includes("/quiz/") && !url.includes("review.php") && !url.includes(".pdf")) || forceDefaultPage ) { // if the current window is any other window than a quiz or pdf. HandleUI(url); } } catch (e) { ShowMessage( { m: texts.fatalError, isSimple: true, }, undefined, () => { OpenErrorPage(e); } ); Exception(e, "script error at main:"); } if (url.includes("eduplayer")) { AddVideoHotkeys(url); } // adding video hotkeys Log(texts.consoleErrorInfo); if (timerStarted) { console.log("Moodle Test Script run time:"); console.timeEnd("main"); timerStarted = false; } if (forceTestPage || forceResultPage || forceDefaultPage) { if (overlay.querySelector("#scriptMessage")) { overlay.querySelector("#scriptMessage").style.background = "green"; } } } // : }}} // : Main logic stuff {{{ // : Loading {{{ function HandleQminingSite(url) { try { const idInput = document.getElementById("cid"); if (idInput) { idInput.value = getVal("clientId"); } } catch (e) { console.info("Error filling client ID input", e); } try { const sideLinks = document.getElementById("sideBarLinks"); if (!sideLinks) { return; } Array.from(sideLinks.childNodes).forEach(link => { link.addEventListener("mousedown", () => { FillFeedbackCID(url, link); }); }); FillFeedbackCID( url, document .getElementById("sideBarLinks") .getElementsByClassName("active")[0] ); } catch (e) { console.info("Error filling client ID input", e); } } function FillFeedbackCID(url, link) { try { if (link.id === "feedback") { const cidSetInterval = setInterval(() => { const cid = document.getElementById("cid"); if (cid) { cid.value = GetId() + "|" + info().script.version; window.clearInterval(cidSetInterval); } }, 100); } } catch (e) { console.info("Error filling client ID input", e); } } function Init() { const url = location.href; // eslint-disable-line if (url.includes(serverAdress.split("/")[2])) { HandleQminingSite(url); return; } // if (false) { // // eslint-disable-line // setVal("version16", undefined); // setVal("version15", undefined); // setVal("firstRun", undefined); // setVal("showQuestions", undefined); // setVal("showSplash", undefined); // } // -------------------------------------------------------------------------------------- // event listener fuckery // -------------------------------------------------------------------------------------- try { // adding addeventlistener stuff, for the ability to add more event listeners for the same event addEventListener = (function() { if (document.addEventListener) { return function(element, event, handler) { element.addEventListener(event, handler, false); }; } else { return function(element, event, handler) { element.attachEvent("on" + event, handler); }; } })(); } catch (e) { Exception(e, "script error at addEventListener:"); } VersionActions(); if (!url.includes(".pdf")) { ShowMenu(); } ConnectToServer(AfterLoad); } function Auth(pw) { SendXHRMessage("login", { pw: pw, script: true }).then(res => { if (res.result === "success") { ConnectToServer(AfterLoad); ClearAllMessages(); resetMenu(); } else { SafeGetElementById("infoMainDiv", elem => { elem.innerText = texts.invalidPW + pw; }); } }); } function resetMenu() { SafeGetElementById("menuButtonDiv", elem => { elem.style.backgroundColor = "#262626"; }); SafeGetElementById("ircButton", elem => { elem.style.display = "none"; }); SafeGetElementById("retryButton", elem => { elem.style.display = "none"; }); SafeGetElementById("loginDiv", elem => { elem.style.display = "none"; }); SafeGetElementById("infoMainDiv", elem => { elem.innerText = texts.loading; }); } function ConnectToServer(cwith) { ClearAllMessages(); GetXHRInfos() .then(inf => { if (inf.result === "nouser") { NoUserAction(); return; } lastestVersion = inf.version; motd = inf.motd; subjInfo = inf.subjinfo; overlay.querySelector( "#infoMainDiv" ).innerText = `${subjInfo.subjects} tárgy, ${subjInfo.questions} kérdés. Felh #${inf.uid}`; // FIXME: if cwith() throws an unhandled error it sais server is not avaible cwith(); }) .catch(() => { NoServerAction(); }); } function NoUserAction() { SafeGetElementById("menuButtonDiv", elem => { elem.style.backgroundColor = "#44cc00"; }); SafeGetElementById("infoMainDiv", elem => { elem.innerText = texts.noUser; if (getVal("clientId")) { elem.innerText += ` (${getVal("clientId")})`; } }); SafeGetElementById("loginDiv", elem => { elem.style.display = ""; }); } function NoServerAction() { SafeGetElementById("menuButtonDiv", elem => { elem.style.backgroundColor = "red"; }); SafeGetElementById("infoMainDiv", elem => { elem.innerText = texts.noServer; }); SafeGetElementById("ircButton", elem => { elem.style.display = ""; }); SafeGetElementById("retryButton", elem => { elem.style.display = ""; }); Log(texts.noServerConsoleMessage); } function VersionActions() { // FOR TESTING ONLY // setVal("version15", true); // setVal("firstRun", true); // setVal("version16", true); // throw "asd"; FreshStart(); } // : Version action functions {{{ function FreshStart() { var firstRun = getVal("firstRun"); // if the current run is the frst if (firstRun === undefined || firstRun === true) { setVal("firstRun", false); ShowHelp(); // showing help document.write(texts.freshStartWarning); document.close(); throw new Error("something, so this stuff stops"); } } // : }}} // : UI handling {{{ function HandleUI(url) { // FIXME: normal string building with localisation :/ var newVersion = false; // if the script is newer than last start try { newVersion = info().script.version !== getVal("lastVerson"); } catch (e) { Log("Some weird error trying to set new verison"); } let showMOTD = false; if (!SUtils.EmptyOrWhiteSpace(motd)) { var prevmotd = getVal("motd"); if (prevmotd !== motd) { showMOTD = true; setVal("motdcount", motdShowCount); setVal("motd", motd); } else { var motdcount = getVal("motdcount"); if (motdcount === undefined) { setVal("motdcount", motdShowCount); motdcount = motdShowCount; } motdcount--; if (motdcount > 0) { showMOTD = true; setVal("motdcount", motdcount); } } } let isNewVersionAvaible = lastestVersion !== undefined && info().script.version !== lastestVersion; var greetMsg = ""; // message to show at the end var timeout = null; // the timeout. if null, it wont be hidden if (isNewVersionAvaible || newVersion || showMOTD) { greetMsg = "Moodle/Elearning/KMOOC segéd v. " + info().script.version + ". "; } if (isNewVersionAvaible) { timeout = 5; greetMsg += "Új verzió elérhető: " + lastestVersion; timeout = undefined; } if (newVersion) { // -------------------------------------------------------------------------------------------------------------- greetMsg += "Verzió frissítve " + info().script.version + "-re. Changelog:\n" + texts.lastChangeLog; setVal("lastVerson", info().script.version); // setting lastVersion } if (showMOTD) { greetMsg += "\nMOTD:\n" + motd; timeout = null; } ShowMessage( { m: greetMsg, isSimple: true, }, timeout ); // showing message. If "m" is empty it wont show it, thats how showSplash works. } // : }}} // : Answering stuffs {{{ function HandleQuiz() { var q = QPM.GetQuestionFromTest(); var questions = q.q; var imgNodes = q.imgnodes; // ------------------------------------------------------------------------------------------------------ let promises = []; questions.forEach(x => { let question = SUtils.EmptyOrWhiteSpace(x) ? "" : SUtils.RemoveUnnecesarySpaces(x); // simplifying question promises.push( GetXHRQuestionAnswer({ q: question, data: GetImageDataFromImgNodes(imgNodes), subj: MPM.GetCurrentSubjectName(), }) ); }); // FIXME: promise.all promise resolve order same as original? Promise.all(promises).then(res => { let answers = []; res.forEach((result, j) => { var r = PrepareAnswers(result, j); if (r !== undefined) { answers.push(r); } HighLightAnswer(result, j); // highlights the answer for the current result }); ShowAnswers(answers, q.q); }); } function PrepareAnswers(result, j) { assert(result); if (result.length > 0) { var allMessages = []; // preparing all messages for (var k = 0; k < result.length; k++) { var msg = ""; // the current message if ( getVal("showQuestions") === undefined || getVal("showQuestions") ) { msg += result[k].q.Q + "\n"; // adding the question if yes } msg += result[k].q.A.replace(/, /g, "\n"); // adding answer if (result[k].q.data.type === "image") { msg += "\n\nKépek fenti válaszok sorrendjében: " + result[k].q.data.images.join(", "); // if it has image part, adding that too } if ( result[k].detailedMatch && result[k].detailedMatch.matchedSubjName ) { msg += "\n(Tárgy: " + result[k].detailedMatch.matchedSubjName + ")"; } allMessages.push({ m: msg, p: result[k].match, }); } return allMessages; } } function ShowAnswers(answers, question) { assert(answers); if (answers.length > 0) { // if there are more than 0 answer ShowMessage(answers); } else { ShowMessage( { m: texts.noResult, isSimple: true, }, undefined, function() { OpenErrorPage({ message: "No result found", stack: JSON.stringify(question), }); } ); } } // : }}} // : Quiz saving {{{ function HandleResults(url) { SaveQuiz(GetQuiz(), ShowSaveQuizDialog); // saves the quiz questions and answers } function ShowSaveQuizDialog(sendResult, sentData, newQuestions) { // FIXME: normal string building with localisation :/ var msg = ""; if (sendResult) { msg = "Kérdések elküldve, katt az elküldött adatokért."; if (newQuestions > 0) { msg += " " + newQuestions + " új kérdés"; } else { msg += " Nincs új kérdés"; } } else { msg = "Szerver nem elérhető, vagy egyéb hiba kérdések elküldésénél! (F12 -> Console)"; } // showing a message wit the click event, and the generated page ShowMessage( { m: msg, isSimple: true, }, null, function() { let towrite = ""; try { towrite += "

Elküldött adatok:

" + JSON.stringify(sentData); } catch (e) { towrite += "

Elküldött adatok:

" + sentData; } document.write(towrite); document.close(); } ); } // this should get the image url from a result page // i is the index of the question // FIXME: move this to RPM class ??? and refactor this function GetImageFormResult(i) { try { var imgElements = RPM.GetResultImage(i); // trying to get image var imgURL = []; // image urls for (var j = 0; j < imgElements.length; j++) { if (!imgElements[j].src.includes("brokenfile")) { var filePart = imgElements[j].src.split("/"); // splits the link by "/" filePart = filePart[filePart.length - 1]; // the last one is the image name imgURL.push(decodeURI(SUtils.ShortenString(filePart, 30))); } } if (imgURL.length > 0) { return imgURL; } } catch (e) { Log("Couldn't get images from result"); } } function GetDataFormResult(i) { let data = { type: "simple" }; let img = GetImageFormResult(i); let grabbox = RPM.GetAnswersFromGrabBox(i); if (img) { data = { type: "image", images: img, }; } if (grabbox) { data = { type: "grabbox", images: img, grabbox: grabbox, }; } return data; } // saves the current quiz. questionData contains the active subjects questions function SaveQuiz(quiz, next) { try { let sentData = {}; if (quiz.length === 0) { throw new Error("quiz length is zero!"); } try { try { sentData.subj = MPM.GetCurrentSubjectName(); } catch (e) { sentData.subj = "NOSUBJ"; Log("unable to get subject name :c"); } sentData.version = info().script.version; sentData.id = GetId(); sentData.quiz = quiz; console.log("SENT DATA", sentData); SendXHRMessage("isAdding", sentData).then(res => { next(res.success, sentData, res.newQuestions); }); } catch (e) { Exception(e, "error at sending data to server."); } } catch (e) { Exception(e, "script error at saving quiz"); } } // getting quiz from finish page function GetQuiz() { try { var quiz = []; // final quiz stuff var results = RPM.GetFormResult(); // getting results element for (var i = 0; i < results.length - 2; i++) { var question = {}; // the current question // QUESTION -------------------------------------------------------------------------------------------------------------------- question.Q = RPM.GetQuestionFromResult(i); // RIGHTANSWER --------------------------------------------------------------------------------------------------------------------- question.A = RPM.GetRightAnswerFromResultv2(i); if (question.A === undefined) { question.A = RPM.GetRightAnswerFromResult(i); } // DATA --------------------------------------------------------------------------------------------------------------------- question.data = GetDataFormResult(i); if (question.A !== undefined) { quiz.push(question); // adding current question to quiz } else { Log( "error getting queston, no correct answer given, or its incorrect" ); Log(question); } } return quiz; } catch (e) { Exception(e, "script error at quiz parsing:"); } } // : }}} // : Helpers {{{ function GetImageDataFromImgNodes(imgs) { var questionImages = []; // the array for the image names in question for (var i = 0; i < imgs.length; i++) { if (!imgs[i].src.includes("brokenfile")) { var filePart = imgs[i].src.split("/"); // splits the link by "/" filePart = filePart[filePart.length - 1]; // the last one is the image name questionImages.push( decodeURI( SUtils.RemoveUnnecesarySpaces( SUtils.ShortenString(filePart, 30) ) ) ); // decodes uri codes, and removes exess spaces, and shortening it } } if (questionImages.length > 0) { return { type: "image", images: questionImages, }; } else { return { type: "simple", }; } } // adds image names to image nodes function AddImageNamesToImages(imgs) { for (var i = 0; i < imgs.length; i++) { if (!imgs[i].src.includes("brokenfile")) { var filePart = imgs[i].src.split("/"); // splits the link by "/" filePart = filePart[filePart.length - 1]; // the last one is the image name var appedtTo = imgs[i].parentNode; // it will be appended here var mainDiv = document.createElement("div"); var fileName = SUtils.ShortenString(decodeURI(filePart), 15); // shortening name, couse it can be long as fuck var textNode = document.createTextNode("(" + fileName + ")"); mainDiv.appendChild(textNode); appedtTo.appendChild(mainDiv); } } } // this function adds basic hotkeys for video controll. function AddVideoHotkeys(url) { var seekTime = 20; document.addEventListener("keydown", function(e) { try { var video = MPM.GetVideo(); var keyCode = e.keyCode; // getting keycode if (keyCode === 32) { // if the keycode is 32 (space) e.preventDefault(); // preventing default action (space scrolles down) if (video.paused && video.buffered.length > 0) { video.play(); } else { video.pause(); } } if (keyCode === 39) { // rigth : 39 video.currentTime += seekTime; } if (keyCode === 37) { // left : 37 video.currentTime -= seekTime; } } catch (err) { Log("Hotkey error."); Log(err.message); } }); var toadd = MPM.GetVideoElement(); var node = CreateNodeWithText(toadd, texts.videoHelp); node.style.margin = "5px 5px 5px 5px"; // fancy margin } // removes stuff like " a. q1" -> "q1" function RemoveLetterMarking(inp) { let dotIndex = inp.indexOf("."); let doubledotIndex = inp.indexOf(":"); let maxInd = 4; // inp.length * 0.2; if (dotIndex < maxInd) { return SUtils.RemoveUnnecesarySpaces( inp.substr(inp.indexOf(".") + 1, inp.length) ); } else if (doubledotIndex < maxInd) { return SUtils.RemoveUnnecesarySpaces( inp.substr(inp.indexOf(":") + 1, inp.length) ); } else { return inp; } } // highlights the possible solutions to the current question function HighLightAnswer(results, currQuestionNumber) { try { if (results.length > 0) { var answers = RPM.GetAllAnswer(currQuestionNumber); // getting all answers var toColor = []; // the numberth in the array will be colored, and .length items will be colored var type = ""; // type of the question. radio or ticbox or whatitscalled for (let i = 0; i < answers.length; i++) { // going thtough answers if ( answers[i].tagName && answers[i].tagName.toLowerCase() === "div" ) { // if its not null and is "div" var correct = results[0].q.A.toLowerCase(); // getting current correct answer from data var answer = answers[i].innerText .replace(/\n/g, "") .toLowerCase(); // getting current answer // removing stuff like "a." answer = RemoveLetterMarking(answer); if ( SUtils.EmptyOrWhiteSpace(correct) || SUtils.EmptyOrWhiteSpace(answer) ) { continue; } if ( SUtils.NormalizeSpaces( SUtils.RemoveUnnecesarySpaces(correct) ).includes(answer) ) { // if the correct answer includes the current answer toColor.push(i); // adding the index type = MPM.GetInputType(answers, i); // setting the type } } } if (results[0].match === 100) { // if the result is 100% correct if (type !== "radio" || toColor.length === 1) { // FIXME why not radio for (let i = 0; i < toColor.length; i++) { // going through "toColor" let highlight = createHoverOver( answers[toColor[i]] ); Object.assign(highlight.style, { border: "7px solid rgba(100, 240, 100, 0.8)", borderRadius: "10px", margin: "-13px 0 0 -8px", }); } } } // and coloring the correct index } } catch (e) { // catching errors. Sometimes there are random errors, wich i did not test, but they are rare, and does not break the main script. Log("script error at highlightin answer: " + e.message); } } // : }}} function Log(value) { if (log) { console.log(value); } } function Exception(e, msg) { Log("------------------------------------------"); Log(msg); Log(e.message); Log("------------------------------------------"); Log(e.stack); Log("------------------------------------------"); } // : }}} // : Minor UI stuff {{{ function ClearAllMessages() { overlay.querySelectorAll("#scriptMessage").forEach(x => x.remove()); } // shows a message with "msg" text, "matchPercent" tip and transp, and "timeout" time function ShowMessage(msgItem, timeout, funct) { // msgItem help: // [ [ {}{}{}{} ] [ {}{}{} ] ] // msgItem[] <- a questions stuff // msgItem[][] <- a questions relevant answers array // msgItem[][].p <- a questions precent // msgItem[][].m <- a questions message try { var defMargin = "0px 5px 0px 5px"; var isSimpleMessage = false; var simpleMessageText = ""; if (msgItem.isSimple) { // parsing msgItem for easier use simpleMessageText = msgItem.m; if (simpleMessageText === "") { // if msg is empty return; } msgItem = [ [ { m: simpleMessageText, }, ], ]; isSimpleMessage = true; } var appedtTo = overlay; // will be appended here var width = window.innerWidth - window.innerWidth / 6; // with of the box var startFromTop = 25; // top distance var mainDiv = document.createElement("div"); // the main divider, wich items will be attached to mainDiv.setAttribute("id", "messageMainDiv"); if (funct) { // if there is a function as parameter addEventListener(mainDiv, "click", funct); // adding it as click } // lotsa crap style SetStyle(mainDiv, { position: "fixed", zIndex: 999999, textAlign: "center", width: width + "px", padding: "0px", background: "#222d32", color: "#ffffff", borderColor: "#035a8f", border: "none", top: startFromTop + "px", left: (window.innerWidth - width) / 2 + "px", opacity: "0.9", }); mainDiv.setAttribute("id", "scriptMessage"); var matchPercent = msgItem[0][0].p; if (isSimpleMessage) { var simpleMessageParagrapg = document.createElement("p"); // new paragraph simpleMessageParagrapg.style.margin = defMargin; // fancy margin var mesageNode = document.createElement("p"); // new paragraph mesageNode.innerHTML = simpleMessageText.replace( /\n/g, "
" ); simpleMessageParagrapg.appendChild(mesageNode); mesageNode.style.margin = defMargin; // fancy margin mainDiv.appendChild(simpleMessageParagrapg); // adding text box to main div } else { // if its a fucking complicated message // TABLE SETUP ------------------------------------------------------------------------------------------------------------ var table = document.createElement("table"); table.style.width = "100%"; // ROWS ----------------------------------------------------------------------------------------------------- var rowOne = table.insertRow(); // previous suggestion, question text, and prev question var rowTwo = table.insertRow(); // next question button var rowThree = table.insertRow(); // next suggetsion button // CELLS ----------------------------------------------------------------------------------------------------- // row one var numberTextCell = rowOne.insertCell(); var questionCell = rowOne.insertCell(); // QUESTION CELL questionCell.setAttribute("id", "questionCell"); questionCell.rowSpan = 3; questionCell.style.width = "90%"; var prevQuestionCell = rowOne.insertCell(); // row two var percentTextCell = rowTwo.insertCell(); var nextQuestionCell = rowTwo.insertCell(); // row three var prevSuggestionCell = rowThree.insertCell(); var nextSuggestionCell = rowThree.insertCell(); // adding finally mainDiv.appendChild(table); // PERCENT TEXT SETUP ----------------------------------------------------------------------------------------------------- var percentTextBox = CreateNodeWithText(percentTextCell, ""); percentTextBox.setAttribute("id", "percentTextBox"); if (matchPercent) { // if match percent param is not null percentTextBox.innerText = matchPercent + "%"; } // NUMBER SETUP ----------------------------------------------------------------------------------------------------- var numberTextBox = CreateNodeWithText(numberTextCell, "1."); numberTextBox.setAttribute("id", "numberTextBox"); // ANSWER NODE SETUP ------------------------------------------------------------------------------------------------------------- var questionTextElement = CreateNodeWithText( questionCell, "ur question goes here, mister OwO" ); questionTextElement.setAttribute("id", "questionTextElement"); // BUTTON SETUP ----------------------------------------------------------------------------------------------------------- var currItem = 0; var currRelevantQuestion = 0; const GetRelevantQuestion = () => { // returns the currItemth questions currRelevantQuestionth relevant question return msgItem[currItem][currRelevantQuestion]; }; const ChangeCurrItemIndex = to => { currItem += to; if (currItem < 0) { currItem = 0; } if (currItem > msgItem.length - 1) { currItem = msgItem.length - 1; } currRelevantQuestion = 0; }; const ChangeCurrRelevantQuestionIndex = to => { currRelevantQuestion += to; if (currRelevantQuestion < 0) { currRelevantQuestion = 0; } if (currRelevantQuestion > msgItem[currItem].length - 1) { currRelevantQuestion = msgItem[currItem].length - 1; } }; const SetQuestionText = () => { var relevantQuestion = GetRelevantQuestion(); questionTextElement.innerText = relevantQuestion.m; if (currItem === 0 && currRelevantQuestion === 0) { numberTextBox.innerText = currRelevantQuestion + 1 + "."; } else { numberTextBox.innerText = currItem + 1 + "./" + (currRelevantQuestion + 1) + "."; } percentTextBox.innerText = relevantQuestion.p + "%"; }; var buttonMargin = "2px 2px 2px 2px"; // uniform button margin if (msgItem[currItem].length > 1) { // PREV SUGG BUTTON ------------------------------------------------------------------------------------------------------------ var prevSuggButton = CreateNodeWithText( prevSuggestionCell, "<", "button" ); prevSuggButton.style.margin = buttonMargin; // fancy margin prevSuggButton.addEventListener("click", function() { ChangeCurrRelevantQuestionIndex(-1); SetQuestionText(); }); // NEXT SUGG BUTTON ------------------------------------------------------------------------------------------------------------ var nextSuggButton = CreateNodeWithText( nextSuggestionCell, ">", "button" ); nextSuggButton.style.margin = buttonMargin; // fancy margin nextSuggButton.addEventListener("click", function() { ChangeCurrRelevantQuestionIndex(1); SetQuestionText(); }); } // deciding if has multiple questions ------------------------------------------------------------------------------------------------ if (msgItem.length === 1) { SetQuestionText(); } else { // if there are multiple items to display // PREV QUESTION BUTTON ------------------------------------------------------------------------------------------------------------ var prevButton = CreateNodeWithText( prevQuestionCell, "^", "button" ); prevButton.style.margin = buttonMargin; // fancy margin // event listener prevButton.addEventListener("click", function() { ChangeCurrItemIndex(-1); SetQuestionText(); }); // NEXT QUESTION BUTTON ------------------------------------------------------------------------------------------------------------ var nextButton = CreateNodeWithText( nextQuestionCell, "ˇ", "button" ); nextButton.style.margin = buttonMargin; // fancy margin // event listener nextButton.addEventListener("click", function() { ChangeCurrItemIndex(1); SetQuestionText(); }); SetQuestionText(); } } appedtTo.appendChild(mainDiv); // THE FINAL APPEND // setting some events // addEventListener(window, 'scroll', function () { // mainDiv.style.top = (pageYOffset + startFromTop) + 'px'; // }) addEventListener(window, "resize", function() { mainDiv.style.left = (window.innerWidth - width) / 2 + "px"; }); var timeOut; if (timeout && timeout > 0) { // setting timeout if not zero or null timeOut = setTimeout(function() { mainDiv.parentNode.removeChild(mainDiv); }, timeout * 1000); } // middle click close event listener addEventListener(mainDiv, "mousedown", function(e) { if (e.which === 2) { mainDiv.parentNode.removeChild(mainDiv); if (timeOut) { clearTimeout(timeOut); } } }); } catch (e) { Exception(e, "script error at showing message:"); } } // shows a fancy menu function ShowMenu() { try { var appedtTo = overlay; // will be appended here // mainDiv.style.left = (window.innerWidth - width) / 2 + 'px'; var menuButtonDiv = document.createElement("div"); menuButtonDiv.setAttribute("id", "menuButtonDiv"); SetStyle(menuButtonDiv, { width: "600px", // height: buttonHeight + 'px', top: window.innerHeight - 120 + "px", left: "10px", zIndex: 999999, position: "fixed", textAlign: "center", padding: "0px", margin: "0px", background: "#262626", }); var tbl = document.createElement("table"); tbl.style.margin = "5px 5px 5px 5px"; tbl.style.textAlign = "left"; tbl.style.width = "98%"; menuButtonDiv.appendChild(tbl); var buttonRow = tbl.insertRow(); var buttonCell = buttonRow.insertCell(); buttonCell.style.textAlign = "center"; let buttonStyle = { position: "", margin: "5px 5px 5px 5px", border: "none", backgroundColor: "#222d32", color: "#ffffff", cursor: "pointer", }; // help button ---------------------------------------------------------------------------------------------------------------- let helpButton = CreateNodeWithText( buttonCell, texts.help, "button" ); SetStyle(helpButton, buttonStyle); helpButton.addEventListener("click", function() { ShowHelp(); }); // adding clicktextNode // site link ---------------------------------------------------------------------------------------------------------------- let contributeLink = CreateNodeWithText( buttonCell, texts.contribute, "button" ); contributeLink.title = texts.contributeTitle; SetStyle(contributeLink, buttonStyle); contributeLink.addEventListener("click", function() { openInTab(serverAdress + "contribute?scriptMenu", { active: true, }); }); // pw request ---------------------------------------------------------------------------------------------------------------- let pwRequest = CreateNodeWithText( buttonCell, texts.pwRequest, "button" ); pwRequest.title = texts.newPWTitle; SetStyle(pwRequest, buttonStyle); pwRequest.addEventListener("click", function() { openInTab(serverAdress + "pwRequest", { active: true, }); }); // site link ---------------------------------------------------------------------------------------------------------------- let siteLink = CreateNodeWithText( buttonCell, texts.websiteBugreport, "button" ); SetStyle(siteLink, buttonStyle); siteLink.addEventListener("click", function() { openInTab(serverAdress + "menuClick", { active: true, }); }); // donate link ---------------------------------------------------------------------------------------------------------------- let donateLink = CreateNodeWithText( buttonCell, texts.donate, "button" ); SetStyle(donateLink, buttonStyle); donateLink.addEventListener("click", function() { openInTab(serverAdress + "donate?scriptMenu", { active: true, }); }); addEventListener(window, "resize", function() { menuButtonDiv.style.top = window.innerHeight - 70 + "px"; }); // INFO TABEL -------------------------------------------------------------------- var itbl = document.createElement("table"); SetStyle(itbl, { margin: "5px 5px 5px 5px", textAlign: "left", width: "98%", }); menuButtonDiv.appendChild(itbl); var ibuttonRow = tbl.insertRow(); var ibuttonCell = ibuttonRow.insertCell(); ibuttonCell.style.textAlign = "center"; // INFO DIV --------------------------------------------------------------------------------- let infoDiv = CreateNodeWithText( ibuttonCell, texts.loading, "span" ); infoDiv.setAttribute("id", "infoMainDiv"); SetStyle(infoDiv, { color: "#ffffff", margin: "5px", }); // login div ---------------------------------------------------------------------------------------------------------------- const loginDiv = document.createElement("div"); loginDiv.style.display = "none"; loginDiv.setAttribute("id", "loginDiv"); const loginButton = document.createElement("button"); loginButton.innerText = texts.login; const loginInput = document.createElement("input"); loginInput.type = "text"; loginInput.style.width = "400px"; loginInput.style.textAlign = "center"; const clientId = getVal("clientId"); if (clientId && clientId.toString()[0] !== "0") { loginInput.value = clientId || ""; loginButton.innerText = texts.requestPWInsteadOfLogin; } loginDiv.appendChild(loginInput); loginDiv.appendChild(loginButton); SetStyle(loginButton, buttonStyle); loginInput.addEventListener("keyup", e => { console.log(e.target.value); if (e.target.value === clientId) { loginButton.innerText = texts.requestPWInsteadOfLogin; } else if (e.target.value !== "") { loginButton.innerText = texts.login; } }); loginButton.addEventListener("click", function() { if (loginInput.value === clientId.toString()) { openInTab(serverAdress + "getVeteranPw?cid=" + clientId, { active: true, }); } else { Auth(loginInput.value); } }); ibuttonCell.appendChild(loginDiv); // irc button ---------------------------------------------------------------------------------------------------------------- let ircButton = CreateNodeWithText( ibuttonCell, texts.ircButton, "button" ); SetStyle(ircButton, buttonStyle); ircButton.style.display = "none"; ircButton.setAttribute("id", "ircButton"); ircButton.addEventListener("click", function() { openInTab(ircAddress, { active: true, }); }); // retry button ---------------------------------------------------------------------------------------------------------------- let retryButton = CreateNodeWithText( ibuttonCell, texts.retry, "button" ); SetStyle(retryButton, buttonStyle); retryButton.style.display = "none"; retryButton.setAttribute("id", "retryButton"); retryButton.addEventListener("click", function() { menuButtonDiv.style.background = "#262626"; infoDiv.innerText = texts.loading; retryButton.style.display = "none"; ircButton.style.display = "none"; ConnectToServer(AfterLoad); }); // window resize event listener --------------------------------------- addEventListener(window, "resize", function() { menuButtonDiv.style.top = window.innerHeight - 70 + "px"; }); // APPEND EVERYTHING appedtTo.appendChild(menuButtonDiv); } catch (e) { Exception(e, "script error at showing menu:"); } } // : }}} // : Generic utils {{{ function GetId() { let currId = getVal("clientId"); if (currId) { return currId; } else { currId = new Date(); currId = currId.getTime() + Math.floor(Math.random() * 1000000000000); currId = currId.toString().split(""); currId.shift(); currId = "0" + currId.join(""); setVal("clientId", currId); return currId; } } function SafeGetElementById(id, next) { let element = overlay.querySelector("#" + id); if (element) { next(element); } else { Log(`Unable to safe get element by id: ${id}`); } } function SetStyle(target, style) { Object.keys(style) .sort() .forEach(key => { target.style[key] = style[key]; }); } function CreateNodeWithText(to, text, type) { var paragraphElement = document.createElement(type || "p"); // new paragraph var textNode = document.createTextNode(text); paragraphElement.appendChild(textNode); to.appendChild(paragraphElement); return paragraphElement; } function GetXHRInfos() { const now = new Date().getTime(); const lastCheck = getVal("lastInfoCheckTime"); if (!lastCheck) { setVal("lastInfoCheckTime", now); } let lastInfo = { result: "noLastInfo" }; try { lastInfo = JSON.parse(getVal("lastInfo")); } catch (e) { console.info(e); } if ( lastInfo.result !== "success" || now > lastCheck + infoExpireTime * 1000 ) { return new Promise((resolve, reject) => { const url = apiAdress + "infos?version=true&motd=true&subjinfo=true&cversion=" + info().script.version + "&cid=" + GetId(); xmlhttpRequest({ method: "GET", url: url, crossDomain: true, xhrFields: { withCredentials: true }, headers: { "Content-Type": "application/json", }, onload: function(response) { try { setVal("lastInfoCheckTime", now); const res = JSON.parse(response.responseText); setVal("lastInfo", response.responseText); resolve(res); } catch (e) { Log("Errro paring JSON in GetXHRInfos"); Log(response.responseText); Log(e); reject(e); } }, onerror: e => { Log("Info get Error", e); reject(e); }, }); }); } else { return new Promise((resolve, reject) => { try { resolve(lastInfo); } catch (e) { Log( "Errro paring JSON in GetXHRInfos, when using old data!" ); Log(e); reject(e); } }); } } function GetXHRQuestionAnswer(question) { return new Promise((resolve, reject) => { let url = apiAdress + "ask?"; let params = []; Object.keys(question).forEach(key => { let val = question[key]; if (typeof val !== "string") { val = JSON.stringify(val); } params.push(key + "=" + encodeURIComponent(val)); }); url += params.join("&") + "&cversion=" + info().script.version + "&cid=" + GetId(); xmlhttpRequest({ method: "GET", url: url, onload: function(response) { try { let res = JSON.parse(response.responseText); // FIXME: check if res is a valid answer array // res.json({ // result: r, // success: true // }) // ERROR: // res.json({ // message: `Invalid question :(`, // result: [], // recievedData: JSON.stringify(req.query), // success: false // }) resolve(res.result); } catch (e) { reject(e); } }, onerror: e => { Log("Errro paring JSON in GetXHRQuestionAnswer"); Log(e); reject(e); reject(e); }, }); }); } function SendXHRMessage(path, message) { // message = SUtils.RemoveSpecialChars(message) // TODO: check this if (typeof message === "object") { message = JSON.stringify(message); } const url = apiAdress + path; return new Promise((resolve, reject) => { xmlhttpRequest({ method: "POST", url: url, crossDomain: true, xhrFields: { withCredentials: true }, data: message, headers: { "Content-Type": "application/json", }, onerror: function(e) { Log("Data send error", e); reject(e); }, onload: resp => { try { const res = JSON.parse(resp.responseText); resolve(res); } catch (e) { Log("Error paring JSON in SendXHRMessage"); Log(resp.responseText); Log(e); reject(e); } }, }); }); } function OpenErrorPage(e) { let path = "lred"; try { if (e.message || e.stack) { path += "?"; } if (e.message) { path += "msg:" + SUtils.SimplifyQuery(e.message); } if (e.stack) { path += "___stack:" + SUtils.SimplifyStack(e.stack); } path += "___version:" + info().script.version; path = SUtils.RemoveSpecialChars(path); } catch (e) { Exception(e, "error at setting error stack/msg link"); } path = path.replace(/ /g, "_"); openInTab(serverAdress + path, { active: true, }); } // : }}} // : Help {{{ // shows some neat help function ShowHelp() { openInTab(serverAdress + "manual?scriptMenu", { active: true, }); } // : }}} // I am not too proud to cry that He and he // Will never never go out of my mind. // All his bones crying, and poor in all but pain, // Being innocent, he dreaded that he died // Hating his God, but what he was was plain: // An old kind man brave in his burning pride. // The sticks of the house were his; his books he owned. // Even as a baby he had never cried; // Nor did he now, save to his secret wound. // Out of his eyes I saw the last light glide. // Here among the liught of the lording sky // An old man is with me where I go // Walking in the meadows of his son's eye // Too proud to cry, too frail to check the tears, // And caught between two nights, blindness and death. // O deepest wound of all that he should die // On that darkest day. })(); // eslint-disable-line