From 84cd7ffc58cfc6d29e058d8a4b52329986bf2165 Mon Sep 17 00:00:00 2001 From: mrfry Date: Sun, 22 Nov 2020 09:47:13 +0100 Subject: [PATCH] Error handling, showing more messages to user, localisation improvements --- stable.user.js | 486 ++++++++++++++++++++++++++++++------------------- 1 file changed, 295 insertions(+), 191 deletions(-) diff --git a/stable.user.js b/stable.user.js index 798550b..4605e20 100755 --- a/stable.user.js +++ b/stable.user.js @@ -81,12 +81,12 @@ // : Constants and global variables {{{ const logElementGetting = false - const log = true + const logEnabled = true const showErrors = true // forcing pages for testing. unless you test, do not set these to true! setVal('ISDEVEL', true) // only one of these should be true for testing - const forceTestPage = false + const forceTestPage = true const forceResultPage = false const forceDefaultPage = false @@ -149,12 +149,24 @@ search: 'Keresés ...', loading: 'Betöltés ...', login: 'Belépés', - requestPWInsteadOfLogin: 'Jelszó igénylés', + requestPWInsteadOflogin: 'Jelszó igénylés', newPWTitle: 'Új jelszó új felhasználónak', pwRequest: 'Jelszó új felhasználónak', 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`, + noParseableQuestionResult: + 'A tesztben nem találhatók kérdések, amit fel lehet dolgozni, vagy hiba történt feldolgozásuk közben', + unableToParseTestQuestion: + 'Hiba történt a kérdések beolvasása közben :/ Kattints az üzenetre a manuális kereséshez (új böngésző tab-ban)', + loadingAnswer: 'Válaszok betöltése ...', + reinstallFromCorrectSource: + 'Scriptet nem a weboldalról raktad fel. PLS reinstall aaaaaaaaa', // TODO: new text + versionUpdated: 'Verzió frissítve ', + newVersionAvaible: 'Új verzió elérhető: ', + scriptName: 'Moodle/Elearning/KMOOC segéd ', + userMOTD: 'Felhasználó MOTD (ezt csak te látod):\n', + motd: 'MOTD:\n', } var texts = huTexts @@ -189,17 +201,17 @@ return promises } - function makeTextFromElements(item) { - // TODO! - // if (emptyOrWhiteSpace(item)) { - // return '' - // } + function makeTextFromElements(acc, item) { + if (emptyOrWhiteSpace(item.val)) { + return acc + } if (item.type === 'img') { - return '[' + item.val + ']' + acc.push('[' + item.val + ']') } else { - return item.val + acc.push(item.val) } + return acc } function getImagesFromElements(elements) { @@ -213,7 +225,7 @@ function getCurrentSubjectName() { if (logElementGetting) { - Log('getting current subjects name') + log('getting current subjects name') } return document.getElementById('page-header').innerText.split('\n')[0] || '' } @@ -227,28 +239,53 @@ // : Test page processing functions {{{ function handleQuiz() { - getQuizData().then(res => { - const promises = [] + const { removeMessage: removeLoadingMessage } = ShowMessage({ + m: texts.loadingAnswer, + isSimple: true, + }) - res.forEach(question => { - promises.push( - GetXHRQuestionAnswer({ - q: question.question, - data: question.data, - subj: getCurrentSubjectName(), - }) - ) - }) + getQuizData() + .then(res => { + if (res.length === 0) { + ShowMessage( + { + m: texts.unableToParseTestQuestion, + isSimple: true, + }, + undefined, + () => { + OpenErrorPage({ + message: 'No result found', + }) + } + ) + return + } - Promise.all(promises).then(res => { - const answers = res.map(result => { - return PrepareAnswers(result) + const promises = [] + res.forEach(question => { + promises.push( + GetXHRQuestionAnswer({ + q: question.question, + data: question.data, + subj: getCurrentSubjectName(), + }) + ) }) - // TODO: new lines in answers - ShowAnswers(answers, res[0].question) + Promise.all(promises).then(res => { + const answers = res.map(result => { + return PrepareAnswers(result) + }) + + removeLoadingMessage() + ShowAnswers(answers, res[0].question) + }) + }) + .catch(err => { + warn(err) + warn('Error in handleQuiz()') }) - }) } function getQuizData() { @@ -271,8 +308,14 @@ } Promise.all(promises) - .then(res => { - resolve(res) + .then(result => { + const errorsRemoved = result.reduce((acc, res) => { + if (res.success) { + acc.push(res) + } + return acc + }, []) + resolve(errorsRemoved) }) .catch(err => { console.warn('Error in handleQuiz()') @@ -314,46 +357,58 @@ function getQuestionPromiseForSingleQuestion(node) { return new Promise(resolve => { - const qtextNode = node.getElementsByClassName('qtext')[0] + try { + const qtextNode = node.getElementsByClassName('qtext')[0] - const questionPromises = getTextPromisesFromNode(qtextNode) - const possibleAnswerPromises = getPossibleAnswersFromTest(node) + const questionPromises = getTextPromisesFromNode(qtextNode) + const possibleAnswerPromises = getPossibleAnswersFromTest(node) - const unflattenedPossibleAnswerPromises = possibleAnswerPromises - ? possibleAnswerPromises.map(x => { - return Promise.all(x) + const unflattenedPossibleAnswerPromises = possibleAnswerPromises + ? possibleAnswerPromises.map(x => { + return Promise.all(x) + }) + : [] + + Promise.all([ + Promise.all(questionPromises), + Promise.all(unflattenedPossibleAnswerPromises), + ]) + .then(([question, possibleAnswerArray]) => { + const questionText = removeUnnecesarySpaces( + question.reduce(makeTextFromElements, []).join(' ') + ) + const possibleAnswers = possibleAnswerArray.map(x => { + return removeUnnecesarySpaces( + x.reduce(makeTextFromElements, []).join(' ') + ) + }) + let images = getImagesFromElements([ + ...question, + ...possibleAnswerArray.reduce((acc, x) => { + return [...acc, ...x] + }, []), + ]) + images = uniq(images) + const data = getDataFromTest(images, possibleAnswerArray) + + resolve({ + question: questionText, + possibleAnswers, + images, + data, + success: true, + }) }) - : [] - - Promise.all([ - Promise.all(questionPromises), - Promise.all(unflattenedPossibleAnswerPromises), - ]) - .then(([question, possibleAnswerArray]) => { - const questionText = question.map(makeTextFromElements).join(' ') - const possibleAnswers = possibleAnswerArray.map(x => { - return removeUnnecesarySpaces(x.map(makeTextFromElements).join(' ')) + .catch(err => { + warn('Error in getQuestionPromiseForSingleQuestion()') + warn(err) + resolve({ success: false }) }) - let images = getImagesFromElements([ - ...question, - ...possibleAnswerArray.reduce((acc, x) => { - return [...acc, ...x] - }, []), - ]) - images = uniq(images) - const data = getDataFromTest(images, possibleAnswerArray) - - resolve({ - question: questionText, - possibleAnswers, - images, - data, - }) - }) - .catch(err => { - console.warn('Error in getQuestionPromiseForSingleQuestion()') - console.warn(err) - }) + } catch (err) { + warn('Error in getQuestionPromiseForSingleQuestion()') + warn(err) + resolve({ success: false }) + } }) } @@ -389,11 +444,17 @@ Promise.all(promises) .then(result => { - resolve(result) + const errorsRemoved = result.reduce((acc, res) => { + if (res.success) { + acc.push(res) + } + return acc + }, []) + resolve(errorsRemoved) }) .catch(err => { - console.warn('Error in getQuiz()') - console.warn(err) + warn('Error in getQuiz()') + warn(err) }) }) } @@ -407,10 +468,10 @@ res = getter.getterFunction(node) return true } catch (e) { - Log(`${key} failed`) + log(`${key} failed`) } } else { - Log(`${key} did not pass`) + log(`${key} did not pass`) } }) @@ -429,14 +490,16 @@ ) if (!answerPromises || !questionPromises) { - Log('Answer or question array is empty, skipping question') + log('Answer or question array is empty, skipping question') resolve({ success: false }) } Promise.all([Promise.all(questionPromises), Promise.all(answerPromises)]) .then(([question, answer]) => { - const questionText = question.map(makeTextFromElements).join(' ') - const answerText = answer.map(makeTextFromElements).join(' ') + const questionText = question + .reduce(makeTextFromElements, []) + .join(' ') + const answerText = answer.reduce(makeTextFromElements, []).join(' ') let images = getImagesFromElements([...question, ...answer]) // images = uniq(images) @@ -448,8 +511,9 @@ }) }) .catch(err => { - console.warn('Error in getQuizFromNode()') - console.warn(err) + warn('Error in getQuizFromNode()') + warn(err) + resolve({ success: false }) }) }) } @@ -545,6 +609,16 @@ return 'asd' }, }, + getDragBoxAnswer: { + description: 'Get complex answer', + requirement: node => { + return false + }, + getterFunction: node => { + // TODO dragboxes + return 'asd' + }, + }, } function getIfSolutionIsCorrect(node) { @@ -606,14 +680,14 @@ function getVideo() { if (logElementGetting) { - Log('getting video stuff') + log('getting video stuff') } return document.getElementsByTagName('video')[0] } function getVideoElement() { if (logElementGetting) { - Log('getting video element') + log('getting video element') } return document.getElementById('videoElement').parentNode } @@ -767,18 +841,13 @@ // : Main function {{{ - let timerStarted = false - // window.addEventListener("load", () => {}) Main() function Main() { 'use strict' - // https://qmining.frylabs.net/moodle-test-userscript/stable.user.js?up - console.log('Moodle / E-Learning script') - console.time('main') - timerStarted = true + log('Moodle / E-Learning script') if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', Init) @@ -829,19 +898,7 @@ 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' - } - } + log(texts.consoleErrorInfo) } // : }}} @@ -963,7 +1020,7 @@ NoUserAction() return } - lastestVersion = inf.version + lastestVersion = inf.version.replace(/\n/g, '') motd = inf.motd userSpecificMotd = inf.userSpecificMotd subjInfo = inf.subjinfo @@ -1008,7 +1065,7 @@ SafeGetElementById('retryButton', elem => { elem.style.display = '' }) - Log(texts.noServerConsoleMessage) + log(texts.noServerConsoleMessage) } function VersionActions() { @@ -1018,23 +1075,14 @@ // : }}} // : 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 + function shouldShowMotd() { if (!emptyOrWhiteSpace(motd)) { var prevmotd = getVal('motd') if (prevmotd !== motd) { - showMOTD = true setVal('motdcount', motdShowCount) setVal('motd', motd) + return true } else { var motdcount = getVal('motdcount') if (motdcount === undefined) { @@ -1044,51 +1092,61 @@ motdcount-- if (motdcount > 0) { - showMOTD = true setVal('motdcount', motdcount) + return true } } } + } + + function HandleUI(url) { + 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') + } + + const showMOTD = shouldShowMotd() const showUserSpecificMOTD = !!userSpecificMotd 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 + let timeout = null + const greetMsg = [] if (isNewVersionAvaible || newVersion || showMOTD || showUserSpecificMOTD) { - greetMsg = - 'Moodle/Elearning/KMOOC segéd v. ' + info().script.version + '. ' + greetMsg.push(texts.scriptName + info().script.version) } if (isNewVersionAvaible) { timeout = 5 - greetMsg += 'Új verzió elérhető: ' + lastestVersion + greetMsg.push(texts.newVersionAvaible + lastestVersion) timeout = undefined } if (newVersion) { - greetMsg += 'Verzió frissítve ' + info().script.version + '-re.' + greetMsg.push(texts.versionUpdated + info().script.version) setVal('lastVerson', info().script.version) // setting lastVersion } if (!installedFromCorrectSource(correctSource)) { - greetMsg += - '\nScriptet nem a weboldalról raktad fel. PLS reinstall (a href = ...)' + greetMsg.push(texts.reinstallFromCorrectSource) } if (showMOTD) { - greetMsg += '\nMOTD:\n' + motd + greetMsg.push(texts.motd + motd) timeout = null } if (showUserSpecificMOTD) { - greetMsg += '\nFelhasználó MOTD (ezt csak te látod):\n' + userSpecificMotd + greetMsg.push(texts.userMOTD + userSpecificMotd) timeout = null } ShowMessage( { - m: greetMsg, + m: greetMsg.join('\n'), isSimple: true, }, timeout - ) // showing message. If "m" is empty it wont show it, thats how showSplash works. + ) } // : }}} @@ -1103,7 +1161,8 @@ let msg = res.q.Q + '\n' // TODO: remove this maybe? - msg += res.q.A.replace(/, /g, '\n') // adding answer + // msg += res.q.A.replace(/, /g, '\n') + msg += res.q.A if (res.q.data.type === 'image') { msg += @@ -1192,7 +1251,11 @@ try { let sentData = {} if (quiz.length === 0) { - throw new Error('quiz length is zero!') + ShowMessage({ + m: texts.noParseableQuestionResult, + isSimple: true, + }) + return } try { sentData = { @@ -1204,7 +1267,7 @@ sentData.subj = getCurrentSubjectName() } catch (e) { sentData.subj = 'NOSUBJ' - Log('unable to get subject name :c') + log('unable to get subject name :c') } console.log('SENT DATA', sentData) SendXHRMessage('isAdding', sentData).then(res => { @@ -1225,8 +1288,9 @@ // : Install source checker {{{ function installedFromCorrectSource(source) { + // https://qmining.frylabs.net/moodle-test-userscript/stable.user.js?up // TODO: test this - return info().script.updateURL === correctSource + return info().script.updateURL === correctSource && false } // : }}} @@ -1274,8 +1338,8 @@ video.currentTime -= seekTime } } catch (err) { - Log('Hotkey error.') - Log(err.message) + log('Hotkey error.') + log(err.message) } }) var toadd = getVideoElement() @@ -1295,9 +1359,37 @@ overlay.querySelectorAll('#scriptMessage').forEach(x => x.remove()) } + function getConvertedMessageNode(message) { + const messageNode = document.createElement('p') + const resultNode = document.createElement('p') + messageNode.innerHTML = message.replace(/\n/g, '
') + + Array.from(messageNode.childNodes).forEach(node => { + if (node.tagName === 'A') { + let linkNode = document.createElement('span') + SetStyle(linkNode, { + color: 'lightblue', + textDecoration: 'underline', + cursor: 'pointer', + }) + linkNode.innerText = node.innerText + linkNode.addEventListener('mousedown', e => { + e.stopPropagation() + openInTab(node.href, { + active: true, + }) + }) + resultNode.appendChild(linkNode) + } else { + resultNode.appendChild(node) + } + }) + + return resultNode + } + // shows a message with "msg" text, "matchPercent" tip and transp, and "timeout" time function ShowMessage(msgItem, timeout, funct) { - // TODO: simple message not selectable // msgItem help: // [ [ {}{}{}{} ] [ {}{}{} ] ] // msgItem[] <- a questions stuff @@ -1349,32 +1441,38 @@ top: startFromTop + 'px', left: (window.innerWidth - width) / 2 + 'px', opacity: '1', - cursor: 'move', + cursor: funct ? 'pointer' : 'move', }) mainDiv.setAttribute('id', 'scriptMessage') // ------------------------------------------------------------------ // moving msg // ------------------------------------------------------------------ + const movingEnabled = !funct let isMouseDown = false let offset = [0, 0] let mousePosition - mainDiv.addEventListener('mousedown', e => { - isMouseDown = true - offset = [mainDiv.offsetLeft - e.clientX, mainDiv.offsetTop - e.clientY] - }) - mainDiv.addEventListener('mouseup', e => { - isMouseDown = false - }) - mainDiv.addEventListener('mousemove', e => { - if (isMouseDown) { - mousePosition = { - x: e.clientX, - y: e.clientY, + if (movingEnabled) { + mainDiv.addEventListener('mousedown', e => { + isMouseDown = true + offset = [ + mainDiv.offsetLeft - e.clientX, + mainDiv.offsetTop - e.clientY, + ] + }) + mainDiv.addEventListener('mouseup', e => { + isMouseDown = false + }) + mainDiv.addEventListener('mousemove', e => { + if (isMouseDown) { + mousePosition = { + x: e.clientX, + y: e.clientY, + } + mainDiv.style.left = mousePosition.x + offset[0] + 'px' + mainDiv.style.top = mousePosition.y + offset[1] + 'px' } - mainDiv.style.left = mousePosition.x + offset[0] + 'px' - mainDiv.style.top = mousePosition.y + offset[1] + 'px' - } - }) + }) + } const xButton = CreateNodeWithText(null, '❌', 'div') SetStyle(xButton, { @@ -1382,6 +1480,7 @@ position: 'absolute', right: '0px', display: 'inline', + margin: '3px', }) xButton.addEventListener('mousedown', e => { e.stopPropagation() @@ -1395,16 +1494,15 @@ 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, '
') + const mesageNode = getConvertedMessageNode(simpleMessageText) + simpleMessageParagrapg.appendChild(mesageNode) mesageNode.addEventListener('mousedown', e => { e.stopPropagation() }) - // TODO: edit this margin to make simple message draggable SetStyle(mesageNode, { - margin: '10px 0px', - cursor: 'auto', + margin: '10px 100px', + cursor: funct ? 'pointer' : 'auto', }) Array.from(mesageNode.getElementsByTagName('a')).forEach(anchorElem => { @@ -1491,7 +1589,7 @@ }) SetStyle(questionTextElement, { - cursor: 'auto', + cursor: funct ? 'pointer' : 'auto', }) questionTextElement.setAttribute('id', 'questionTextElement') @@ -1636,6 +1734,13 @@ } catch (e) { Exception(e, 'script error at showing message:') } + + return { + messageElement: mainDiv, + removeMessage: () => { + mainDiv.parentNode.removeChild(mainDiv) + }, + } } // shows a fancy menu @@ -1668,6 +1773,7 @@ position: 'absolute', right: '0px', display: 'inline', + margin: '5px', }) xButton.addEventListener('mousedown', e => { e.stopPropagation() @@ -1836,7 +1942,7 @@ const clientId = getVal('clientId') if (clientId && clientId.toString()[0] !== '0') { loginInput.value = clientId || '' - loginButton.innerText = texts.requestPWInsteadOfLogin + loginButton.innerText = texts.requestPWInsteadOflogin } loginDiv.appendChild(loginInput) loginDiv.appendChild(loginButton) @@ -1845,7 +1951,7 @@ loginInput.addEventListener('keyup', e => { if (e.target.value === clientId) { - loginButton.innerText = texts.requestPWInsteadOfLogin + loginButton.innerText = texts.requestPWInsteadOflogin } else if (e.target.value !== '') { loginButton.innerText = texts.login } @@ -1914,7 +2020,6 @@ // : String utils 2 {{{ function removeUnnecesarySpaces(toremove) { - // TODO: check if this doesnt kill if question / answer is empty if (!toremove) { return '' } @@ -1959,19 +2064,31 @@ } } - function Log(value) { - if (log) { - console.log(value) + function logHelper(logMethod, value) { + if (logEnabled) { + if (typeof value === 'string') { + logMethod('[Moodle Script]: ' + value) + } else { + logMethod(value) + } } } + function warn(value) { + logHelper(console.warn, value) + } + + function log(value) { + logHelper(console.log, value) + } + function Exception(e, msg) { - Log('------------------------------------------') - Log(msg) - Log(e.message) - Log('------------------------------------------') - Log(e.stack) - Log('------------------------------------------') + log('------------------------------------------') + log(msg) + log(e.message) + log('------------------------------------------') + log(e.stack) + log('------------------------------------------') } function GetId() { @@ -1994,7 +2111,7 @@ if (element) { next(element) } else { - Log(`Unable to safe get element by id: ${id}`) + log(`Unable to safe get element by id: ${id}`) } } @@ -2058,14 +2175,14 @@ setVal('lastInfo', response.responseText) resolve(res) } catch (e) { - Log('Errro paring JSON in GetXHRInfos') - Log(response.responseText) - Log(e) + log('Errro paring JSON in GetXHRInfos') + log(response.responseText) + log(e) reject(e) } }, onerror: e => { - Log('Info get Error', e) + log('Info get Error', e) reject(e) }, }) @@ -2075,8 +2192,8 @@ try { resolve(lastInfo) } catch (e) { - Log('Errro paring JSON in GetXHRInfos, when using old data!') - Log(e) + log('Errro paring JSON in GetXHRInfos, when using old data!') + log(e) reject(e) } }) @@ -2107,26 +2224,14 @@ 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) + log('Errro paring JSON in GetXHRQuestionAnswer') + log(e) reject(e) reject(e) }, @@ -2135,7 +2240,6 @@ } function SendXHRMessage(path, message) { - // message = SUtils.RemoveSpecialChars(message) // TODO: check this if (typeof message === 'object') { message = JSON.stringify(message) } @@ -2151,7 +2255,7 @@ 'Content-Type': 'application/json', }, onerror: function(e) { - Log('Data send error', e) + log('Data send error', e) reject(e) }, onload: resp => { @@ -2159,9 +2263,9 @@ const res = JSON.parse(resp.responseText) resolve(res) } catch (e) { - Log('Error paring JSON in SendXHRMessage') - Log(resp.responseText) - Log(e) + log('Error paring JSON in SendXHRMessage') + log(resp.responseText) + log(e) reject(e) } },