diff --git a/.prettierrc.js b/.prettierrc.js index b0a616d..6cfb93a 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,6 +1,6 @@ module.exports = { trailingComma: "es5", - tabWidth: 4, + tabWidth: 2, semi: false, - singleQuote: false, + singleQuote: true, } diff --git a/stable.user.js b/stable.user.js index b4cad23..c3fdceb 100755 --- a/stable.user.js +++ b/stable.user.js @@ -52,2461 +52,2378 @@ // TODO: test if this ; does not fuck up things ;(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 */ + // 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" + 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 = true // TODO: remove - const forceResultPage = false - const forceDefaultPage = false - const logElementGetting = false - const log = true + // forcing pages for testing. unless you test, do not set these to true! + // only one of these should be true for testing + const forceTestPage = true // TODO: remove + 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 + 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 - setVal("ISDEVEL", true) // TODO: remove + setVal('ISDEVEL', true) // TODO: remove - if (getVal("ISDEVEL")) { - console.log("Moodle script running in developement mode!") - infoExpireTime = 1 - serverAdress = "http://localhost:8080/" - apiAdress = "http://localhost:8080/" + if (getVal('ISDEVEL')) { + console.log('Moodle script running in developement mode!') + infoExpireTime = 1 + serverAdress = 'http://localhost:8080/' + apiAdress = 'http://localhost:8080/' + } + + 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 } - 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`, + SimplifyQuery(q) { + assert(q) + + var result = q.replace(/\n/g, ' ').replace(/\s/g, ' ') + return this.RemoveUnnecesarySpaces(result) } - var texts = huTexts + ShortenString(toShorten, ammount) { + assert(toShorten) - // : question-classes {{{ - const specialChars = ["&", "\\+"] - - const assert = val => { - if (!val) { - throw new Error("Assertion failed") - } + var result = '' + var i = 0 + while (i < toShorten.length && i < ammount) { + result += toShorten[i] + i++ + } + return result } - class StringUtils { - RemoveStuff(value, removableStrings, toReplace) { - removableStrings.forEach(x => { - var regex = new RegExp(x, "g") - value = value.replace(regex, toReplace || "") - }) - return value - } + ReplaceCharsWithSpace(val, char) { + assert(val) + assert(char) - SimplifyQuery(q) { - assert(q) + var toremove = this.NormalizeSpaces(val) - var result = q.replace(/\n/g, " ").replace(/\s/g, " ") - return this.RemoveUnnecesarySpaces(result) - } + var regex = new RegExp(char, 'g') + toremove = toremove.replace(regex, ' ') - 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) - } + return this.RemoveUnnecesarySpaces(toremove) } - const SUtils = new StringUtils() + // removes whitespace from begining and and, and replaces multiple spaces with one space + RemoveUnnecesarySpaces(toremove) { + assert(toremove) - // : }}} - - // : 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 + toremove = this.NormalizeSpaces(toremove) + while (toremove.includes(' ')) { + toremove = toremove.replace(/ {2}/g, ' ') + } + return toremove.trim() } - const overlay = StealthOverlay() + RemoveSpecialChars(value) { + assert(value) - function appendBelowElement(el, toAppend) { - const rect = el.getBoundingClientRect() - const left = rect.left + window.scrollX - const top = rect.top + window.scrollY - - SetStyle(toAppend, { - position: "absolute", - zIndex: 999999, - top: top + "px", - left: left + "px", - }) - - overlay.appendChild(toAppend) + return this.RemoveStuff(value, specialChars, ' ') } - 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 + // 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, ' ') === '' + ) } - 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 - } + // damn nonbreaking space + NormalizeSpaces(input) { + assert(input) - 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, - } - } + return input.replace(/\s/g, ' ') } - class ResultsPageModell { - GetFormulationClearfix() { - if (logElementGetting) { - Log("getting formulation clearfix lol") - } - return document.getElementsByClassName("formulation clearfix") + 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 appendBelowElement(el, toAppend) { + const rect = el.getBoundingClientRect() + const left = rect.left + window.scrollX + const top = rect.top + window.scrollY + + SetStyle(toAppend, { + position: 'absolute', + zIndex: 999999, + top: top + 'px', + left: left + 'px', + }) + + overlay.appendChild(toAppend) + } + + 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 + } - GetGrade(i) { - if (logElementGetting) { - Log("getting grade") - } - const fcf = QPM.GetFormulationClearfix()[i] - return fcf.parentNode.parentNode.childNodes[0].childNodes[2] - .innerText - } + GetAllQuestionsQtext() { + if (logElementGetting) { + Log('getting all questions qtext') + } + return document + .getElementById('responseform') + .getElementsByClassName('qtext') // getting questions + } - DetermineQuestionType(nodes) { - let qtype = "" - let i = 0 + GetAllQuestionsP() { + if (logElementGetting) { + Log('getting all questions by tag p') + } + return document.getElementById('responseform').getElementsByTagName('p') + } - while (i < nodes.length && qtype === "") { - let inps = nodes[i].getElementsByTagName("input") + GetFormulationClearfix() { + if (logElementGetting) { + Log('getting formulation clearfix lol') + } + return document.getElementsByClassName('formulation clearfix') + } - if (inps.length > 0) { - qtype = inps[0].type - } + GetAnswerOptions() { + if (logElementGetting) { + Log('getting all answer options') + } + return this.GetFormulationClearfix()[0].childNodes[3].innerText + } - i++ - } + GetQuestionImages() { + if (logElementGetting) { + Log('getting question images') + } + return this.GetFormulationClearfix()[0].getElementsByTagName('img') + } - 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++) { + // 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 ( - r[j].tagName !== undefined && - r[j].tagName.toLowerCase() === "div" + subAllQuestions[i].data !== undefined && + !SUtils.EmptyOrWhiteSpace(subAllQuestions[i].data) ) { - ret.push(r[j]) + questionData += subAllQuestions[i].data + ' ' // adding text to question data } + } } - - 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 - - // window.addEventListener("load", () => {}) - Main() - - function Main() { - "use strict" - console.log("Moodle / E-Learning script") - console.time("main") - timerStarted = true - - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", Init) + questions = [questionData] + } else { + questions = [ddq] + } } else { - Init() + 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') } - 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) - } + GetGrade(i) { + if (logElementGetting) { + Log('getting grade') + } + const fcf = QPM.GetFormulationClearfix()[i] + return fcf.parentNode.parentNode.childNodes[0].childNodes[2].innerText } - 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) + 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 } - 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) + 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 + } } - 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 - }) - } + 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 } - 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") + 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) + } } - // : }}} - - // : 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. + GetRightAnswerIfCorrectShown(i) { + if (logElementGetting) { + Log('getting right answer if correct shown') + } + var results = this.GetFormResult() // getting results element + return results[i].getElementsByClassName('rightanswer') } - // : }}} - - // : 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) - }) + 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 '' + } } - 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 - } + 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 + } } - 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), - }) - } - ) - } + GetFormCFOfResult(result) { + if (logElementGetting) { + Log('getting formulation clearfix') + } + return result.getElementsByClassName('formulation clearfix')[0] } - // : }}} - - // : Quiz saving {{{ - - function HandleResults(url) { - SaveQuiz(GetQuiz(), ShowSaveQuizDialog) // saves the quiz questions and answers + GetResultText(i) { + if (logElementGetting) { + Log('getting result text') + } + var results = this.GetFormResult() // getting results element + return this.GetFormCFOfResult(results[i]).getElementsByTagName('p') } - 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() - } - ) + GetResultImage(i) { + if (logElementGetting) { + Log('getting result image') + } + var results = this.GetFormResult() // getting results element + return this.GetFormCFOfResult(results[i]).getElementsByTagName('img') } - // this should get the image url from a result page + // gets the question from the 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, + 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 { - return { - type: "simple", - } + try { + currQuestion = this.GetCurrQuestion(i) + } catch (e) { + currQuestion = 'REEEEEEEEEEEEEEEEEEEEE' // this shouldnt really happen sry guys + Log('Unable to get question in GetQuestionFromResult') + } } + } + return currQuestion } - // adds image names to image nodes - function AddImageNamesToImages(imgs) { - for (var i = 0; i < imgs.length; i++) { - if (!imgs[i].src.includes("brokenfile")) { - // TODO: add this to shadowroot - var filePart = imgs[i].src.split("/") // splits the link by "/" - // console.log(imgs[i].src.split("base64,")[1]) - // TODO: base64 - filePart = filePart[filePart.length - 1] // the last one is the image name - var appendTo = 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) - appendBelowElement(appendTo, mainDiv) - } + // 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 } - // 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) - } + 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 + + // window.addEventListener("load", () => {}) + 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) }) - var toadd = MPM.GetVideoElement() - var node = CreateNodeWithText(toadd, texts.videoHelp) - node.style.margin = "5px 5px 5px 5px" // fancy margin + }) + + 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 } - // 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) - ) + // 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 inp + 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) + } - // 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 + 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 + }) + } + }) + } - // removing stuff like "a." - answer = RemoveLetterMarking(answer) + 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 + }) + } - 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 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') } - // : }}} - - 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 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 } - let lastInfo = { result: "noLastInfo" } - try { - lastInfo = JSON.parse(getVal("lastInfo")) - } catch (e) { - console.info(e) + 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 ( - lastInfo.result !== "success" || - now > lastCheck + infoExpireTime * 1000 + result[k].detailedMatch && + result[k].detailedMatch.matchedSubjName ) { - 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) - } - }) + msg += '\n(Tárgy: ' + result[k].detailedMatch.matchedSubjName + ')' } - } - - 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) - }, - }) + allMessages.push({ + m: msg, + p: result[k].match, }) + } + return allMessages } + } - function SendXHRMessage(path, message) { - // message = SUtils.RemoveSpecialChars(message) // TODO: check this - if (typeof message === "object") { - message = JSON.stringify(message) + 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), + }) } - 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" + // : }}} + + // : 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 { - 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) + towrite += '

Elküldött adatok:

' + JSON.stringify(sentData) } catch (e) { - Exception(e, "error at setting error stack/msg link") + towrite += '

Elküldött adatok:

' + sentData } - path = path.replace(/ /g, "_") - openInTab(serverAdress + path, { - active: true, - }) + 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 + } - // : Help {{{ - - // shows some neat help - function ShowHelp() { - openInTab(serverAdress + "manual?scriptMenu", { - active: true, + // 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')) { + // TODO: add this to shadowroot + var filePart = imgs[i].src.split('/') // splits the link by "/" + // console.log(imgs[i].src.split("base64,")[1]) + // TODO: base64 + filePart = filePart[filePart.length - 1] // the last one is the image name + var appendTo = 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) + appendBelowElement(appendTo, 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() - // 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, + 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) + } + }) + } + } - // 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. + 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() - // 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. + 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) + }, + }) + }) + } - // 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 + 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) + } + }, + }) + }) + } - // 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. + 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, + }) + } - // O deepest wound of all that he should die - // On that darkest day. + // : }}} + + // : 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