mirror of
https://gitlab.com/MrFry/moodle-test-userscript
synced 2025-04-01 20:22:48 +02:00
2177 lines
65 KiB
JavaScript
Executable file
2177 lines
65 KiB
JavaScript
Executable file
/* ----------------------------------------------------------------------------
|
|
|
|
Online Moodle/Elearning/KMOOC test help
|
|
Greasyfork: <https://greasyfork.org/en/scripts/38999-moodle-elearning-kmooc-test-help>
|
|
GitLab: <https://gitlab.com/MrFry/moodle-test-userscript>
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
------------------------------------------------------------------------- */
|
|
|
|
// ==UserScript==
|
|
// @name Moodle/Elearning/KMOOC test help
|
|
// @version 2.0.1.21
|
|
// @description Online Moodle/Elearning/KMOOC test help
|
|
// @author MrFry
|
|
// @match https://elearning.uni-obuda.hu/main/*
|
|
// @match https://elearning.uni-obuda.hu/kmooc/*
|
|
// @match https://mooc.unideb.hu/*
|
|
// @match https://itc.semmelweis.hu/moodle/*
|
|
// @match https://qmining.frylabs.net/*
|
|
// @match http://qmining.frylabs.net/*
|
|
// @noframes
|
|
// @run-at document-start
|
|
// @grant GM_getResourceText
|
|
// @grant GM_info
|
|
// @grant GM_getValue
|
|
// @grant GM_setValue
|
|
// @grant GM_deleteValue
|
|
// @grant GM_xmlhttpRequest
|
|
// @grant GM_openInTab
|
|
// @grant unsafeWindow
|
|
// @license GNU General Public License v3.0 or later
|
|
// @supportURL qmining.frylabs.net
|
|
// @contributionURL qmining.frylabs.net
|
|
// @namespace https://qmining.frylabs.net
|
|
// @updateURL https://qmining.frylabs.net/moodle-test-userscript/stable.user.js?up
|
|
// ==/UserScript==
|
|
//
|
|
// TODO:
|
|
// grabboxes test on quiz page
|
|
|
|
// TODO: test if this ; does not fuck up things (it seams it does not)
|
|
;(function() {
|
|
// eslint-disable-line
|
|
// GM functions, only to disable ESLINT errors
|
|
/* eslint-disable */
|
|
const a = Main
|
|
const usf = unsafeWindow
|
|
function getVal(name) {
|
|
return GM_getValue(name)
|
|
}
|
|
function setVal(name, val) {
|
|
return GM_setValue(name, val)
|
|
}
|
|
function delVal(name) {
|
|
return GM_deleteValue(name)
|
|
}
|
|
function openInTab(address, options) {
|
|
GM_openInTab(address, options)
|
|
}
|
|
function xmlhttpRequest(opts) {
|
|
GM_xmlhttpRequest(opts)
|
|
}
|
|
function info() {
|
|
return GM_info
|
|
}
|
|
/* eslint-enable */
|
|
|
|
var addEventListener // add event listener function
|
|
let serverAdress = 'https://qmining.frylabs.net/'
|
|
let apiAdress = 'https://api.frylabs.net/'
|
|
const ircAddress = 'https://kiwiirc.com/nextclient/irc.sub.fm/#qmining'
|
|
|
|
// forcing pages for testing. unless you test, do not set these to true!
|
|
// only one of these should be true for testing
|
|
setVal('ISDEVEL', true)
|
|
const forceTestPage = true
|
|
const forceResultPage = false
|
|
const forceDefaultPage = false
|
|
const logElementGetting = false
|
|
const log = true
|
|
const showErrors = true
|
|
|
|
const motdShowCount = 3 /* Ammount of times to show motd */
|
|
let infoExpireTime = 60 // Every n seconds basic info should be loaded from server
|
|
var uid = 0
|
|
var cid = 0
|
|
var motd = ''
|
|
var userSpecificMotd = ''
|
|
var lastestVersion = ''
|
|
var subjInfo
|
|
|
|
// array, where elems are added to shadow-root, but its position should be at target.
|
|
var updatableElements = [] // { elem: ..., target: ... }
|
|
var elementUpdaterInterval = -1
|
|
const overlayElemUpdateInterval = 2 // seconds
|
|
|
|
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! https://qmining.frylabs.net/manual?scriptcmd',
|
|
freshStartWarning:
|
|
'<h1>Moodle teszt userscript:<h1><h3>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!</h3> <h3>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!!!</h3><h5>Ez az ablak frissités után eltűnik. Ha nem, akkor a visza gombbal próbálkozz.</h5>',
|
|
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',
|
|
contribute: 'Bug report / Szavazás következő feature-re',
|
|
donate: 'Donate',
|
|
retry: 'Újrapróbálás',
|
|
ircButton: 'IRC',
|
|
ircButtonTitle: 'IRC chat',
|
|
invalidPW: 'Hibás jelszó: ',
|
|
search: 'Keresés ...',
|
|
loading: 'Betöltés ...',
|
|
login: 'Belépés',
|
|
requestPWInsteadOfLogin: 'Jelszó igénylés',
|
|
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')
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------------
|
|
// Basic processing helpers
|
|
// ----------------------------------------------------------------------------------------------
|
|
|
|
function getTextPromisesFromNode(node) {
|
|
const promises = []
|
|
Array.from(node.childNodes).forEach(elem => {
|
|
let img = elem
|
|
if (elem.tagName !== 'IMG') {
|
|
const t = elem.tagName ? elem.getElementsByTagName('img') : []
|
|
if (t.length > 0) {
|
|
img = t[0]
|
|
}
|
|
}
|
|
|
|
if (img.tagName === 'IMG') {
|
|
promises.push(digestMessage(getBase64Image(img)))
|
|
} else if (elem.tagName === undefined) {
|
|
promises.push({ type: 'txt', val: elem.nodeValue })
|
|
} else {
|
|
promises.push({ type: 'txt', val: elem.innerText })
|
|
}
|
|
})
|
|
return promises
|
|
}
|
|
|
|
function makeTextFromElements(item) {
|
|
// TODO!
|
|
// if (emptyOrWhiteSpace(item)) {
|
|
// return ''
|
|
// }
|
|
|
|
if (item.type === 'img') {
|
|
return '[' + item.val + ']'
|
|
} else {
|
|
return item.val
|
|
}
|
|
}
|
|
|
|
function getImagesFromElements(elements) {
|
|
return elements.reduce((acc, element) => {
|
|
if (element.type === 'img') {
|
|
acc.push(element.val)
|
|
}
|
|
return acc
|
|
}, [])
|
|
}
|
|
|
|
function getCurrentSubjectName() {
|
|
if (logElementGetting) {
|
|
Log('getting current subjects name')
|
|
}
|
|
return document.getElementById('page-header').innerText.split('\n')[0] || ''
|
|
}
|
|
|
|
function uniq(a) {
|
|
return [...new Set(a)]
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------------
|
|
// Test page processing functions
|
|
// ----------------------------------------------------------------------------------------------
|
|
|
|
function handleQuiz() {
|
|
getQuizData().then(res => {
|
|
const promises = []
|
|
|
|
res.forEach(question => {
|
|
promises.push(
|
|
GetXHRQuestionAnswer({
|
|
q: question.question,
|
|
data: question.data,
|
|
subj: getCurrentSubjectName(),
|
|
})
|
|
)
|
|
})
|
|
|
|
Promise.all(promises).then(res => {
|
|
const answers = res.map(result => {
|
|
return PrepareAnswers(result)
|
|
})
|
|
|
|
// TODO: new lines in answers
|
|
ShowAnswers(answers, res[0].question)
|
|
})
|
|
})
|
|
}
|
|
|
|
function getQuizData() {
|
|
return new Promise(resolve => {
|
|
// TODO: dropdown in question
|
|
// TODO: get possible answers too
|
|
const promises = []
|
|
const questionNodes = Array.from(
|
|
document.getElementsByTagName('form')[0].childNodes[0].childNodes
|
|
)
|
|
|
|
let i = 0
|
|
while (
|
|
i < questionNodes.length &&
|
|
questionNodes[i].tagName === 'DIV' &&
|
|
questionNodes[i].className !== 'submitbtns'
|
|
) {
|
|
promises.push(getQuestionPromiseForSingleQuestion(questionNodes[i]))
|
|
i++
|
|
}
|
|
|
|
Promise.all(promises)
|
|
.then(res => {
|
|
resolve(res)
|
|
})
|
|
.catch(err => {
|
|
console.warn('Error in handleQuiz()')
|
|
console.warn(err)
|
|
})
|
|
})
|
|
}
|
|
|
|
function getPossibleAnswersFromTest(node) {
|
|
const promises = []
|
|
const answerRoot = node.getElementsByClassName('answer')[0]
|
|
|
|
if (answerRoot.tagName === 'DIV') {
|
|
const answers = Array.from(answerRoot.childNodes)
|
|
|
|
answers.forEach(answer => {
|
|
if (answer.tagName) {
|
|
promises.push(getTextPromisesFromNode(answer))
|
|
}
|
|
})
|
|
|
|
return promises
|
|
} else if (answerRoot.tagName === 'TABLE') {
|
|
const answers = Array.from(answerRoot.childNodes[0].childNodes)
|
|
|
|
answers.forEach(answer => {
|
|
if (answer.tagName) {
|
|
promises.push(
|
|
getTextPromisesFromNode(answer.getElementsByClassName('text')[0])
|
|
)
|
|
// here elements with classname 'control' could be added too. Those should be a dropdown,
|
|
// containing possible choices
|
|
}
|
|
})
|
|
|
|
return promises
|
|
}
|
|
}
|
|
|
|
function getQuestionPromiseForSingleQuestion(node) {
|
|
return new Promise(resolve => {
|
|
const qtextNode = node.getElementsByClassName('qtext')[0]
|
|
|
|
const questionPromises = getTextPromisesFromNode(qtextNode)
|
|
const possibleAnswerPromises = getPossibleAnswersFromTest(node)
|
|
|
|
const unflattenedPossibleAnswerPromises = possibleAnswerPromises
|
|
? possibleAnswerPromises.map(x => {
|
|
return Promise.all(x)
|
|
})
|
|
: []
|
|
|
|
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(' '))
|
|
})
|
|
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)
|
|
})
|
|
})
|
|
}
|
|
|
|
function getDataFromTest(images) {
|
|
if (images.length > 0) {
|
|
return {
|
|
type: 'image',
|
|
images: images,
|
|
}
|
|
} else {
|
|
return {
|
|
type: 'simple',
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------------
|
|
// Result page processing functions
|
|
// ----------------------------------------------------------------------------------------------
|
|
|
|
function getQuiz() {
|
|
return new Promise(resolve => {
|
|
const promises = []
|
|
const questionNodes = Array.from(
|
|
document.getElementsByTagName('form')[0].childNodes[0].childNodes
|
|
)
|
|
|
|
let i = 0
|
|
while (i < questionNodes.length && questionNodes[i].tagName === 'DIV') {
|
|
promises.push(getQuizFromNode(questionNodes[i]))
|
|
i++
|
|
}
|
|
|
|
// [{
|
|
// "Q": "Mekkora tényezővel kell számolnunk, ha 100.000 Ft jelenértékét keressük 24% kamatláb, havi tőkésítés és 2,5 éves futamidő mellett?\n\n\n",
|
|
// "A": "c.\n\n0,552\n",
|
|
// "data": {
|
|
// "type": "simple"
|
|
// }
|
|
// }]
|
|
Promise.all(promises)
|
|
.then(result => {
|
|
resolve(result)
|
|
})
|
|
.catch(err => {
|
|
console.warn('Error in getQuiz()')
|
|
console.warn(err)
|
|
})
|
|
})
|
|
}
|
|
|
|
function getPromisesThatMeetsRequirements(getters, node) {
|
|
let res
|
|
Object.keys(getters).some(key => {
|
|
const getter = getters[key]
|
|
if (getter.requirement(node)) {
|
|
try {
|
|
res = getter.getterFunction(node)
|
|
return true
|
|
} catch (e) {
|
|
Log(`${key} failed`)
|
|
}
|
|
} else {
|
|
Log(`${key} did not pass`)
|
|
}
|
|
})
|
|
|
|
return res
|
|
}
|
|
|
|
function getQuizFromNode(node) {
|
|
return new Promise(resolve => {
|
|
const questionPromises = getPromisesThatMeetsRequirements(
|
|
questionGetters,
|
|
node
|
|
)
|
|
const answerPromises = getPromisesThatMeetsRequirements(
|
|
answerGetters,
|
|
node
|
|
)
|
|
|
|
if (!answerPromises || !questionPromises) {
|
|
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(' ')
|
|
let images = getImagesFromElements([...question, ...answer])
|
|
// images = uniq(images)
|
|
|
|
resolve({
|
|
Q: removeUnnecesarySpaces(questionText),
|
|
A: removeUnnecesarySpaces(answerText),
|
|
data: getData(images),
|
|
success: true,
|
|
})
|
|
})
|
|
.catch(err => {
|
|
console.warn('Error in getQuizFromNode()')
|
|
console.warn(err)
|
|
})
|
|
})
|
|
}
|
|
|
|
function getData(images) {
|
|
if (!images || images.length === 0) {
|
|
return {
|
|
type: 'simple',
|
|
}
|
|
} else {
|
|
return {
|
|
type: 'image',
|
|
images: images,
|
|
}
|
|
}
|
|
}
|
|
|
|
const questionGetters = {
|
|
getSimpleQuestion: {
|
|
description: 'Basic question getter',
|
|
index: 0,
|
|
requirement: node => {
|
|
return node.getElementsByClassName('qtext').length > 0
|
|
},
|
|
getterFunction: node => {
|
|
let question = node.getElementsByClassName('qtext')[0]
|
|
return getTextPromisesFromNode(question)
|
|
},
|
|
},
|
|
}
|
|
|
|
const answerGetters = {
|
|
getSimpleAnswer: {
|
|
description: 'Basic answer getter',
|
|
index: 0,
|
|
requirement: node => {
|
|
return node.getElementsByClassName('rightanswer').length > 0
|
|
},
|
|
getterFunction: node => {
|
|
let answer = node.getElementsByClassName('rightanswer')[0]
|
|
return getTextPromisesFromNode(answer)
|
|
},
|
|
},
|
|
noCorrect: {
|
|
description: 'Gets correct answer, even if the correct is not shown',
|
|
index: 2,
|
|
requirement: node => {
|
|
return (
|
|
node.getElementsByClassName('rightanswer').length === 0 &&
|
|
node.getElementsByClassName('answer').length > 0
|
|
)
|
|
},
|
|
getterFunction: node => {
|
|
const possibleAnswers = getPossibleAnswers(node)
|
|
|
|
if (getIfSolutionIsCorrect(node)) {
|
|
if (possibleAnswers.length === 2) {
|
|
return [
|
|
{
|
|
type: 'txt',
|
|
val: possibleAnswers.find(x => {
|
|
return x.selectedByUser === false
|
|
}).text,
|
|
},
|
|
]
|
|
}
|
|
} else {
|
|
return [
|
|
{
|
|
type: 'txt',
|
|
val: possibleAnswers.find(x => {
|
|
return x.selectedByUser === true
|
|
}).text,
|
|
},
|
|
]
|
|
}
|
|
},
|
|
},
|
|
getDropdownAnswer: {
|
|
description: 'Dropdown answer getter',
|
|
index: 1,
|
|
requirement: node => {
|
|
return false
|
|
},
|
|
getterFunction: node => {
|
|
// TODO dropdown kérdés.html
|
|
return 'asd'
|
|
},
|
|
},
|
|
getTextareaAnswer: {
|
|
description: 'Get complex answer',
|
|
index: 1,
|
|
requirement: node => {
|
|
return false
|
|
},
|
|
getterFunction: node => {
|
|
// TODO Ugrás... bug.html
|
|
return 'asd'
|
|
},
|
|
},
|
|
}
|
|
|
|
function getIfSolutionIsCorrect(node) {
|
|
const gradeText = node.getElementsByClassName('grade')[0].innerText
|
|
const stateText = node.getElementsByClassName('state')[0].innerText
|
|
return stateText.includes('Helyes') || !gradeText.includes('0,00')
|
|
}
|
|
|
|
function getPossibleAnswers(node) {
|
|
const answerNodes = Array.from(
|
|
node.getElementsByClassName('answer')[0].childNodes
|
|
)
|
|
|
|
return answerNodes.reduce((acc, answerNode) => {
|
|
let selectedByUser
|
|
if (answerNode.childNodes.length > 0) {
|
|
selectedByUser = answerNode.childNodes[0].checked
|
|
}
|
|
|
|
acc.push({
|
|
text: answerNode.innerText,
|
|
selectedByUser: selectedByUser,
|
|
})
|
|
return acc
|
|
}, [])
|
|
}
|
|
|
|
function digestMessage(message) {
|
|
return new Promise(resolve => {
|
|
const encoder = new TextEncoder()
|
|
const data = encoder.encode(message)
|
|
const hash = crypto.subtle.digest('SHA-256', data).then(buf => {
|
|
let res = String.fromCharCode.apply(null, new Uint8Array(buf))
|
|
res = btoa(res)
|
|
.replace(/=/g, '')
|
|
.replace(/\+/g, '-')
|
|
.replace(/\//g, '_')
|
|
resolve({ type: 'img', val: res })
|
|
})
|
|
})
|
|
}
|
|
|
|
function getBase64Image(img) {
|
|
const copy = document.createElement('img')
|
|
copy.src = img.src
|
|
copy.crossOrigin = 'Anonymous'
|
|
let canvas = document.createElement('canvas')
|
|
canvas.width = copy.width
|
|
canvas.height = copy.height
|
|
let ctx = canvas.getContext('2d')
|
|
ctx.drawImage(copy, 0, 0)
|
|
let dataURL = canvas.toDataURL('image/png')
|
|
return dataURL.replace(/^data:image\/(png|jpg);base64,/, '')
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------------
|
|
// String utils 2
|
|
// ----------------------------------------------------------------------------------------------
|
|
|
|
function removeUnnecesarySpaces(toremove) {
|
|
// TODO: check if this doesnt kill if question / answer is empty
|
|
if (!toremove) {
|
|
return ''
|
|
}
|
|
|
|
toremove = normalizeSpaces(toremove).replace(/\t/g, '')
|
|
while (toremove.includes(' ')) {
|
|
toremove = toremove.replace(/ {2}/g, ' ')
|
|
}
|
|
while (toremove.includes('\n\n')) {
|
|
toremove = toremove.replace(/\n{2}/g, ' ')
|
|
}
|
|
return toremove.trim()
|
|
}
|
|
|
|
function normalizeSpaces(input) {
|
|
assert(input)
|
|
|
|
return input.replace(/\s/g, ' ')
|
|
}
|
|
|
|
function emptyOrWhiteSpace(value) {
|
|
if (value === undefined) {
|
|
return true
|
|
}
|
|
|
|
return (
|
|
value
|
|
.replace(/\n/g, '')
|
|
.replace(/\t/g, '')
|
|
.replace(/ /g, '')
|
|
.replace(/\s/g, ' ') === ''
|
|
)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------------
|
|
|
|
// : }}}
|
|
|
|
// : 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, [`<div><slot></slot></div>`])
|
|
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(appendTo) {
|
|
const overlayElement = document.createElement('div')
|
|
overlay.append(overlayElement)
|
|
|
|
updatableElements.push({ elem: overlayElement, target: appendTo })
|
|
|
|
if (elementUpdaterInterval === -1) {
|
|
elementUpdaterInterval = setInterval(() => {
|
|
updatableElements.forEach(({ elem, target }) => {
|
|
let currX, currY, currWidth, currHeight
|
|
let { left, top, width, height } = target.getBoundingClientRect()
|
|
left += window.scrollX
|
|
top += window.scrollY
|
|
|
|
SetStyle(elem, {
|
|
pointerEvents: 'none',
|
|
userSelect: 'none',
|
|
position: 'absolute',
|
|
zIndex: 999999,
|
|
top: top + 'px',
|
|
left: left + 'px',
|
|
width: width + 'px',
|
|
height: height - 10 + 'px',
|
|
})
|
|
})
|
|
}, overlayElemUpdateInterval * 1000)
|
|
}
|
|
|
|
return overlayElement
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------------
|
|
// Misc
|
|
// ----------------------------------------------------------------------------------------------
|
|
|
|
function getVideo() {
|
|
if (logElementGetting) {
|
|
Log('getting video stuff')
|
|
}
|
|
return document.getElementsByTagName('video')[0]
|
|
}
|
|
|
|
function getVideoElement() {
|
|
if (logElementGetting) {
|
|
Log('getting video element')
|
|
}
|
|
return document.getElementById('videoElement').parentNode
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------------
|
|
|
|
// : }}}
|
|
|
|
// : 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)
|
|
})
|
|
})
|
|
|
|
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
|
|
}
|
|
|
|
try {
|
|
addEventListener = (function() {
|
|
if (document.addEventListener) {
|
|
return function(element, event, handler) {
|
|
element.addEventListener(event, handler, false)
|
|
}
|
|
} else {
|
|
return function(element, event, handler) {
|
|
element.attachEvent('on' + event, handler)
|
|
}
|
|
}
|
|
})()
|
|
} catch (e) {
|
|
Exception(e, 'script error at addEventListener:')
|
|
}
|
|
VersionActions()
|
|
if (!url.includes('.pdf')) {
|
|
ShowMenu()
|
|
}
|
|
ConnectToServer(AfterLoad)
|
|
}
|
|
|
|
function Auth(pw) {
|
|
SendXHRMessage('login', { pw: pw, script: true }).then(res => {
|
|
if (res.result === 'success') {
|
|
ConnectToServer(AfterLoad)
|
|
ClearAllMessages()
|
|
resetMenu()
|
|
} else {
|
|
SafeGetElementById('infoMainDiv', elem => {
|
|
elem.innerText = texts.invalidPW + pw
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
function resetMenu() {
|
|
SafeGetElementById('menuButtonDiv', elem => {
|
|
elem.style.backgroundColor = '#262626'
|
|
})
|
|
SafeGetElementById('ircButton', elem => {
|
|
elem.style.display = 'none'
|
|
})
|
|
SafeGetElementById('retryButton', elem => {
|
|
elem.style.display = 'none'
|
|
})
|
|
SafeGetElementById('loginDiv', elem => {
|
|
elem.style.display = 'none'
|
|
})
|
|
SafeGetElementById('infoMainDiv', elem => {
|
|
elem.innerText = texts.loading
|
|
})
|
|
}
|
|
|
|
function ConnectToServer(cwith) {
|
|
ClearAllMessages()
|
|
GetXHRInfos()
|
|
.then(inf => {
|
|
if (inf.result === 'nouser') {
|
|
NoUserAction()
|
|
return
|
|
}
|
|
lastestVersion = inf.version
|
|
motd = inf.motd
|
|
userSpecificMotd = inf.userSpecificMotd
|
|
subjInfo = inf.subjinfo
|
|
uid = inf.uid
|
|
cid = getVal('clientId')
|
|
overlay.querySelector(
|
|
'#infoMainDiv'
|
|
).innerText = `${subjInfo.subjects} tárgy, ${subjInfo.questions} kérdés. User ID: ${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() {
|
|
FreshStart()
|
|
}
|
|
|
|
// : Version action functions {{{
|
|
|
|
function FreshStart() {
|
|
var firstRun = getVal('firstRun') // if the current run is the frst
|
|
if (firstRun === undefined || firstRun === true) {
|
|
setVal('firstRun', false)
|
|
ShowHelp() // showing help
|
|
|
|
document.write(texts.freshStartWarning)
|
|
document.close()
|
|
throw new Error('something, so this stuff stops')
|
|
}
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : UI handling {{{
|
|
function HandleUI(url) {
|
|
// FIXME: normal string building with localisation :/
|
|
var newVersion = false // if the script is newer than last start
|
|
|
|
try {
|
|
newVersion = info().script.version !== getVal('lastVerson')
|
|
} catch (e) {
|
|
Log('Some weird error trying to set new verison')
|
|
}
|
|
|
|
let showMOTD = false
|
|
if (!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)
|
|
}
|
|
}
|
|
}
|
|
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
|
|
|
|
if (isNewVersionAvaible || newVersion || showMOTD || showUserSpecificMOTD) {
|
|
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
|
|
}
|
|
if (showUserSpecificMOTD) {
|
|
greetMsg += '\nFelhasználó MOTD (ezt csak te látod):\n' + userSpecificMotd
|
|
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 PrepareAnswers(result) {
|
|
assert(result)
|
|
|
|
if (result.length > 0) {
|
|
return result.map(res => {
|
|
let msg = res.q.Q + '\n'
|
|
|
|
// TODO: remove this maybe?
|
|
msg += res.q.A.replace(/, /g, '\n') // adding answer
|
|
|
|
if (res.q.data.type === 'image') {
|
|
msg +=
|
|
'\n\nKépek fenti válaszok sorrendjében: ' +
|
|
res.q.data.images.join(', ') // if it has image part, adding that too
|
|
}
|
|
|
|
return {
|
|
m: msg,
|
|
p: res.match,
|
|
header: res.detailedMatch.matchedSubjName,
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
function ShowAnswers(answers, question) {
|
|
assert(answers)
|
|
|
|
if (answers.length > 0) {
|
|
ShowMessage(answers)
|
|
} else {
|
|
ShowMessage(
|
|
{
|
|
m: texts.noResult,
|
|
isSimple: true,
|
|
},
|
|
undefined,
|
|
function() {
|
|
OpenErrorPage({
|
|
message: 'No result found',
|
|
question: Array.isArray(question)
|
|
? question[0].replace(/"/g, '').replace(/:/g, '')
|
|
: question,
|
|
})
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : Quiz saving {{{
|
|
|
|
function HandleResults(url) {
|
|
getQuiz().then(res => {
|
|
SaveQuiz(res, ShowSaveQuizDialog) // saves the quiz questions and answers
|
|
})
|
|
}
|
|
|
|
function ShowSaveQuizDialog(sendResult, sentData, newQuestions) {
|
|
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 += '</p>Elküldött adatok:</p> ' + JSON.stringify(sentData)
|
|
} catch (e) {
|
|
towrite += '</p>Elküldött adatok:</p> ' + sentData
|
|
}
|
|
document.write(towrite)
|
|
document.close()
|
|
}
|
|
)
|
|
}
|
|
|
|
// 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 {
|
|
sentData = {
|
|
version: info().script.version,
|
|
id: GetId(),
|
|
quiz: quiz,
|
|
}
|
|
try {
|
|
sentData.subj = getCurrentSubjectName()
|
|
} catch (e) {
|
|
sentData.subj = 'NOSUBJ'
|
|
Log('unable to get subject name :c')
|
|
}
|
|
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')
|
|
}
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : Helpers {{{
|
|
|
|
// this function adds basic hotkeys for video controll.
|
|
function AddVideoHotkeys(url) {
|
|
var seekTime = 20
|
|
document.addEventListener('keydown', function(e) {
|
|
try {
|
|
var video = 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 = 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 removeUnnecesarySpaces(
|
|
inp.substr(inp.indexOf('.') + 1, inp.length)
|
|
)
|
|
} else if (doubledotIndex < maxInd) {
|
|
return removeUnnecesarySpaces(
|
|
inp.substr(inp.indexOf(':') + 1, inp.length)
|
|
)
|
|
} else {
|
|
return inp
|
|
}
|
|
}
|
|
|
|
// highlights the possible solutions to the current question
|
|
function HighLightAnswer(results, currQuestionNumber) {
|
|
// TODO: fix this
|
|
}
|
|
|
|
// : }}}
|
|
|
|
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',
|
|
border: '3px solid #99f',
|
|
borderRadius: '5px',
|
|
top: startFromTop + 'px',
|
|
left: (window.innerWidth - width) / 2 + 'px',
|
|
opacity: '1',
|
|
cursor: 'move',
|
|
})
|
|
mainDiv.setAttribute('id', 'scriptMessage')
|
|
// ------------------------------------------------------------------
|
|
// moving msg
|
|
// ------------------------------------------------------------------
|
|
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,
|
|
}
|
|
mainDiv.style.left = mousePosition.x + offset[0] + 'px'
|
|
mainDiv.style.top = mousePosition.y + offset[1] + 'px'
|
|
}
|
|
})
|
|
|
|
const xrow = document.createElement('div')
|
|
xrow.setAttribute('id', 'msgHeader')
|
|
SetStyle(xrow, {
|
|
height: '20px',
|
|
userSelect: 'none',
|
|
})
|
|
mainDiv.appendChild(xrow)
|
|
|
|
const headerText = CreateNodeWithText(xrow, 'asd', 'div')
|
|
headerText.title = 'Talált kérdés tárgy neve'
|
|
SetStyle(headerText, {
|
|
padding: '2px 5px',
|
|
textAlign: 'center',
|
|
display: 'inline',
|
|
})
|
|
|
|
const xButton = CreateNodeWithText(xrow, '❌', 'div')
|
|
SetStyle(xButton, {
|
|
cursor: 'pointer',
|
|
position: 'absolute',
|
|
right: '0px',
|
|
display: 'inline',
|
|
})
|
|
xButton.addEventListener('mousedown', e => {
|
|
e.stopPropagation()
|
|
mainDiv.parentNode.removeChild(mainDiv)
|
|
})
|
|
|
|
// ------------------------------------------------------------------
|
|
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, '</br>')
|
|
simpleMessageParagrapg.appendChild(mesageNode)
|
|
SetStyle(mesageNode, {
|
|
margin: defMargin,
|
|
})
|
|
|
|
Array.from(mesageNode.getElementsByTagName('a')).forEach(anchorElem => {
|
|
anchorElem.style.color = 'lightblue'
|
|
})
|
|
|
|
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
|
|
const sideCellWidth = 30
|
|
var numberTextCell = rowOne.insertCell()
|
|
SetStyle(numberTextCell, {
|
|
width: sideCellWidth + 'px',
|
|
})
|
|
var questionCell = rowOne.insertCell() // QUESTION CELL
|
|
questionCell.setAttribute('id', 'questionCell')
|
|
questionCell.rowSpan = 3
|
|
var prevQuestionCell = rowOne.insertCell()
|
|
SetStyle(prevQuestionCell, {
|
|
width: sideCellWidth + 'px',
|
|
})
|
|
// row two
|
|
var percentTextCell = rowTwo.insertCell()
|
|
SetStyle(percentTextCell, {
|
|
width: sideCellWidth + 'px',
|
|
})
|
|
var nextQuestionCell = rowTwo.insertCell()
|
|
SetStyle(nextQuestionCell, {
|
|
width: sideCellWidth + 'px',
|
|
})
|
|
// 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')
|
|
percentTextBox.title = 'Egyezés %'
|
|
|
|
if (matchPercent) {
|
|
// if match percent param is not null
|
|
percentTextBox.innerText = matchPercent + '%'
|
|
}
|
|
// NUMBER SETUP -----------------------------------------------------------------------------------------------------
|
|
var numberTextBox = CreateNodeWithText(numberTextCell, '1.')
|
|
numberTextBox.setAttribute('id', 'numberTextBox')
|
|
numberTextBox.title = 'Lehetséges válasz index'
|
|
|
|
// ANSWER NODE SETUP -------------------------------------------------------------------------------------------------------------
|
|
var questionTextElement = CreateNodeWithText(
|
|
questionCell,
|
|
'ur question goes here, mister OwO'
|
|
)
|
|
|
|
questionTextElement.addEventListener('mousedown', e => {
|
|
e.stopPropagation()
|
|
})
|
|
|
|
SetStyle(questionTextElement, {
|
|
cursor: 'auto',
|
|
})
|
|
|
|
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 = () => {
|
|
const relevantQuestion = GetRelevantQuestion()
|
|
questionTextElement.innerText = relevantQuestion.m
|
|
headerText.innerText = relevantQuestion.header
|
|
|
|
if (currItem === 0 && currRelevantQuestion === 0) {
|
|
numberTextBox.innerText = currRelevantQuestion + 1 + '.'
|
|
} else {
|
|
numberTextBox.innerText =
|
|
currItem + 1 + './' + (currRelevantQuestion + 1) + '.'
|
|
}
|
|
percentTextBox.innerText = relevantQuestion.p + '%'
|
|
}
|
|
|
|
const buttonStyle = {
|
|
color: 'white',
|
|
backgroundColor: 'transparent',
|
|
margin: buttonMargin,
|
|
border: 'none',
|
|
fontSize: '30px',
|
|
cursor: 'pointer',
|
|
userSelect: 'none',
|
|
}
|
|
var buttonMargin = '2px 2px 2px 2px' // uniform button margin
|
|
if (msgItem[currItem].length > 1) {
|
|
// PREV SUGG BUTTON ------------------------------------------------------------------------------------------------------------
|
|
var prevSuggButton = CreateNodeWithText(
|
|
prevSuggestionCell,
|
|
'⬅️',
|
|
'div'
|
|
)
|
|
prevSuggButton.title = 'Előző lehetséges válasz'
|
|
SetStyle(prevSuggButton, buttonStyle)
|
|
|
|
prevSuggButton.addEventListener('mousedown', function(e) {
|
|
e.stopPropagation()
|
|
ChangeCurrRelevantQuestionIndex(-1)
|
|
SetQuestionText()
|
|
})
|
|
// NEXT SUGG BUTTON ------------------------------------------------------------------------------------------------------------
|
|
var nextSuggButton = CreateNodeWithText(
|
|
nextSuggestionCell,
|
|
'➡️',
|
|
'div'
|
|
)
|
|
nextSuggButton.title = 'Következő lehetséges válasz'
|
|
SetStyle(nextSuggButton, buttonStyle)
|
|
|
|
nextSuggButton.addEventListener('mousedown', function(e) {
|
|
e.stopPropagation()
|
|
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, '⬆️', 'div')
|
|
SetStyle(prevButton, buttonStyle)
|
|
prevButton.title = 'Előző kérdés'
|
|
|
|
// event listener
|
|
prevButton.addEventListener('click', function() {
|
|
ChangeCurrItemIndex(-1)
|
|
SetQuestionText()
|
|
})
|
|
// NEXT QUESTION BUTTON ------------------------------------------------------------------------------------------------------------
|
|
var nextButton = CreateNodeWithText(nextQuestionCell, '⬇️', 'div')
|
|
SetStyle(nextButton, buttonStyle)
|
|
nextButton.title = 'Előző kérdés'
|
|
|
|
// 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',
|
|
border: '3px solid #99f',
|
|
borderRadius: '5px',
|
|
})
|
|
|
|
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',
|
|
}
|
|
// site link ----------------------------------------------------------------------------------------------------------------
|
|
|
|
let siteLink = CreateNodeWithText(
|
|
buttonCell,
|
|
texts.websiteBugreport,
|
|
'button'
|
|
)
|
|
SetStyle(siteLink, buttonStyle)
|
|
|
|
siteLink.addEventListener('click', function() {
|
|
openInTab(serverAdress + 'menuClick', {
|
|
active: true,
|
|
})
|
|
})
|
|
|
|
// 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'
|
|
)
|
|
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?scriptMenu', {
|
|
active: true,
|
|
})
|
|
})
|
|
// IRC ----------------------------------------------------------------------------------------------------------------
|
|
|
|
let ircButton2 = CreateNodeWithText(buttonCell, texts.ircButton, 'button')
|
|
ircButton2.title = texts.ircButtonTitle
|
|
SetStyle(ircButton2, buttonStyle)
|
|
ircButton2.addEventListener('click', function() {
|
|
openInTab(serverAdress + 'irc?scriptMenu', {
|
|
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 => {
|
|
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)
|
|
|
|
addEventListener(menuButtonDiv, 'mousedown', function(e) {
|
|
if (e.which === 2) {
|
|
menuButtonDiv.parentNode.removeChild(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) {
|
|
if (showErrors) {
|
|
console.info(e)
|
|
}
|
|
}
|
|
if (
|
|
lastInfo.result !== 'success' ||
|
|
now > lastCheck + infoExpireTime * 1000
|
|
) {
|
|
return new Promise((resolve, reject) => {
|
|
const url =
|
|
apiAdress +
|
|
'infos?version=true&motd=true&subjinfo=true&cversion=' +
|
|
info().script.version +
|
|
'&cid=' +
|
|
GetId()
|
|
|
|
xmlhttpRequest({
|
|
method: 'GET',
|
|
url: url,
|
|
crossDomain: true,
|
|
xhrFields: { withCredentials: true },
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
onload: function(response) {
|
|
try {
|
|
setVal('lastInfoCheckTime', now)
|
|
const res = JSON.parse(response.responseText)
|
|
setVal('lastInfo', response.responseText)
|
|
resolve(res)
|
|
} catch (e) {
|
|
Log('Errro paring JSON in GetXHRInfos')
|
|
Log(response.responseText)
|
|
Log(e)
|
|
reject(e)
|
|
}
|
|
},
|
|
onerror: e => {
|
|
Log('Info get Error', e)
|
|
reject(e)
|
|
},
|
|
})
|
|
})
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
try {
|
|
resolve(lastInfo)
|
|
} catch (e) {
|
|
Log('Errro paring JSON in GetXHRInfos, when using old data!')
|
|
Log(e)
|
|
reject(e)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
function GetXHRQuestionAnswer(question) {
|
|
return new Promise((resolve, reject) => {
|
|
let url = apiAdress + 'ask?'
|
|
let params = []
|
|
Object.keys(question).forEach(key => {
|
|
let val = question[key]
|
|
if (typeof val !== 'string') {
|
|
val = JSON.stringify(val)
|
|
}
|
|
params.push(key + '=' + encodeURIComponent(val))
|
|
})
|
|
url +=
|
|
params.join('&') +
|
|
'&cversion=' +
|
|
info().script.version +
|
|
'&cid=' +
|
|
GetId()
|
|
|
|
xmlhttpRequest({
|
|
method: 'GET',
|
|
url: url,
|
|
onload: function(response) {
|
|
try {
|
|
let res = JSON.parse(response.responseText)
|
|
// FIXME: check if res is a valid answer array
|
|
// res.json({
|
|
// result: r,
|
|
// success: true
|
|
// })
|
|
// ERROR:
|
|
// res.json({
|
|
// message: `Invalid question :(`,
|
|
// result: [],
|
|
// recievedData: JSON.stringify(req.query),
|
|
// success: false
|
|
// })
|
|
resolve(res.result)
|
|
} catch (e) {
|
|
reject(e)
|
|
}
|
|
},
|
|
onerror: e => {
|
|
Log('Errro paring JSON in GetXHRQuestionAnswer')
|
|
Log(e)
|
|
reject(e)
|
|
reject(e)
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
function SendXHRMessage(path, message) {
|
|
// message = SUtils.RemoveSpecialChars(message) // TODO: check this
|
|
if (typeof message === 'object') {
|
|
message = JSON.stringify(message)
|
|
}
|
|
const url = apiAdress + path
|
|
return new Promise((resolve, reject) => {
|
|
xmlhttpRequest({
|
|
method: 'POST',
|
|
url: url,
|
|
crossDomain: true,
|
|
xhrFields: { withCredentials: true },
|
|
data: message,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
onerror: function(e) {
|
|
Log('Data send error', e)
|
|
reject(e)
|
|
},
|
|
onload: resp => {
|
|
try {
|
|
const res = JSON.parse(resp.responseText)
|
|
resolve(res)
|
|
} catch (e) {
|
|
Log('Error paring JSON in SendXHRMessage')
|
|
Log(resp.responseText)
|
|
Log(e)
|
|
reject(e)
|
|
}
|
|
},
|
|
})
|
|
})
|
|
}
|
|
|
|
function OpenErrorPage(e) {
|
|
const queries = []
|
|
try {
|
|
Object.keys(e).forEach(key => {
|
|
if (e[key]) {
|
|
queries.push(`${key}=${encodeURIComponent(e[key])}`)
|
|
}
|
|
})
|
|
queries.push('version=' + encodeURIComponent(info().script.version))
|
|
queries.push('uid=' + encodeURIComponent(uid))
|
|
queries.push('cid=' + encodeURIComponent(cid))
|
|
} catch (e) {
|
|
Exception(e, 'error at setting error stack/msg link')
|
|
}
|
|
openInTab(serverAdress + 'lred?' + queries.join('&'), {
|
|
active: true,
|
|
})
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// : Help {{{
|
|
|
|
// shows some neat help
|
|
function ShowHelp() {
|
|
openInTab(serverAdress + 'manual?scriptMenu', {
|
|
active: true,
|
|
})
|
|
}
|
|
|
|
// : }}}
|
|
|
|
// I am not too proud to cry that He and he
|
|
// Will never never go out of my mind.
|
|
// All his bones crying, and poor in all but pain,
|
|
|
|
// Being innocent, he dreaded that he died
|
|
// Hating his God, but what he was was plain:
|
|
// An old kind man brave in his burning pride.
|
|
|
|
// The sticks of the house were his; his books he owned.
|
|
// Even as a baby he had never cried;
|
|
// Nor did he now, save to his secret wound.
|
|
|
|
// Out of his eyes I saw the last light glide.
|
|
// Here among the liught of the lording sky
|
|
// An old man is with me where I go
|
|
|
|
// Walking in the meadows of his son's eye
|
|
// Too proud to cry, too frail to check the tears,
|
|
// And caught between two nights, blindness and death.
|
|
|
|
// O deepest wound of all that he should die
|
|
// On that darkest day.
|
|
})() // eslint-disable-line
|