From 4e9525c1670d88c596322370489117ead90bd70f Mon Sep 17 00:00:00 2001 From: MrFry Date: Fri, 24 Jan 2020 19:44:51 +0100 Subject: [PATCH] Major code clean, XMLHTTP question answer getting --- stable.user.js | 728 +++++++++++++++---------------------------------- 1 file changed, 213 insertions(+), 515 deletions(-) diff --git a/stable.user.js b/stable.user.js index f6d254d..55d8cca 100755 --- a/stable.user.js +++ b/stable.user.js @@ -56,14 +56,13 @@ function info () { return GM_info } /* eslint-enable */ - var data // all data, which is in the resource txt var addEventListener // add event listener function - const serverAdress = 'https://qmining.frylabs.net/' - // const serverAdress = 'http://localhost:8080/' + // const serverAdress = 'https://qmining.frylabs.net/' + const serverAdress = 'http://localhost:8080/' // forcing pages for testing. unless you test, do not set these to true! // only one of these should be true for testing - const forceTestPage = false + const forceTestPage = true const forceResultPage = false const forceDefaultPage = false const logElementGetting = false @@ -98,8 +97,6 @@ var texts = huTexts - const minResultMatchPercent = 99 /* Minimum ammount to consider that two questions match during saving */ - // : question-classes {{{ const commonUselessAnswerParts = [ 'A helyes válasz az ', @@ -114,7 +111,6 @@ const commonUselessStringParts = [',', '\\.', ':', '!', '\\+'] const specialChars = [ '&', '\\+' ] const lengthDiffMultiplier = 10 /* Percent minus for length difference */ - const minMatchAmmount = 60 /* Minimum ammount to consider that two questions match during answering */ const notSameDataTypePenalty = 30 // substracted from match percent if 2 questions are not same type const assert = (val) => { @@ -354,190 +350,6 @@ } } - class Subject { - constructor (n) { - assert(n) - - this.Name = n - this.Questions = [] - this.active = false - } - - setIndex (i) { - this.index = i - } - - getIndex () { - return this.index - } - - get length () { - return this.Questions.length - } - - setActive (val) { - this.active = !!val - } - - getIfActive () { - return this.active - } - - AddQuestion (q) { - assert(q) - - this.Questions.push(q) - } - - getSubjNameWithoutYear () { - let t = this.Name.split(' - ') - if (t[0].match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{1}$/i)) { - return t[1] || this.Name - } else { - return this.Name - } - } - - getYear () { - let t = this.Name.split(' - ')[0] - if (t.match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{1}$/i)) { - return t - } else { - return '' - } - } - - Search (q, data) { - assert(q) - - var r = [] - for (let i = 0; i < this.length; i++) { - let percent = this.Questions[i].Compare(q, data) - if (percent > minMatchAmmount) { - r.push({ - q: this.Questions[i], - match: percent - }) - } - } - - for (let i = 0; i < r.length; i++) { - for (var j = i; j < r.length; j++) { - if (r[i].match < r[j].match) { - var tmp = r[i] - r[i] = r[j] - r[j] = tmp - } - } - } - - return r - } - - toString () { - var r = [] - for (var i = 0; i < this.Questions.length; i++) { r.push(this.Questions[i].toString()) } - return '+' + this.Name + '\n' + r.join('\n') - } - } - - class QuestionDB { - constructor (getVal, setVal, delVal) { - this.Subjects = [] - this.getVal = getVal - this.setVal = setVal - this.delVal = delVal - } - - get length () { - return this.Subjects.length - } - - get activeIndexes () { - return this.Subjects.reduce((acc, item, i) => { - if (item.getIfActive()) { - acc.push(i) - } - return acc - }, []) - } - - GetIfActive (ind) { - return this.Subjects[ind].getIfActive() - } - - ChangeActive (subjName, value) { - this.Subjects.find((x) => { - return x.Name === subjName - }).setActive(value) - - let actives = JSON.parse(getVal('actives')) - if (value) { - actives.push(subjName) - } else { - actives = actives.reduce((acc, item) => { - if (item !== subjName) { - acc.push(item) - } - return acc - }, []) - } - setVal('actives', JSON.stringify(actives)) - } - - AddQuestion (subj, q) { - assert(subj) - - var i = 0 - while (i < this.Subjects.length && this.Subjects[i].Name !== subj) { i++ } - if (i < this.Subjects.length) { this.Subjects[i].AddQuestion(q) } else { - const n = new Subject(subj) - n.AddQuestion(q) - this.Subjects.push(n) - } - } - - Search (q, data) { - assert(q) - - var r = [] - for (let i = 0; i < this.length; i++) { - if (this.GetIfActive(i)) { r = r.concat(this.Subjects[i].Search(q, data)) } - } - - for (let i = 0; i < r.length; i++) { - for (var j = i; j < r.length; j++) { - if (r[i].match < r[j].match) { - var tmp = r[i] - r[i] = r[j] - r[j] = tmp - } - } - } - - return r - } - - AddSubject (subj) { - assert(subj) - - var i = 0 - while (i < this.length && subj.Name !== this.Subjects[i].Name) { i++ } - - if (i < this.length) { - this.Subjects.concat(subj.Questions) - } else { - this.Subjects.push(subj) - } - } - - toString () { - var r = [] - for (var i = 0; i < this.Subjects.length; i++) { r.push(this.Subjects[i].toString()) } - return r.join('\n\n') - } - } - // : }}} // : DOM getting stuff {{{ @@ -919,7 +731,7 @@ class MiscPageModell { GetCurrentSubjectName () { if (logElementGetting) { Log('getting current subjects name') } - return document.getElementById('page-header').innerText.split('\n')[0] + return document.getElementById('page-header').innerText.split('\n')[0] || '' } GetVideo () { @@ -986,7 +798,6 @@ } console.log('Moodle Test Script run time:') console.timeEnd('main') - SetActivesAsJSON() }) if (forceTestPage || forceResultPage || forceDefaultPage) { @@ -1008,7 +819,6 @@ setVal('showSplash', undefined) } var url = location.href // eslint-disable-line - var count = -1 // loaded question count. stays -1 if the load failed. // -------------------------------------------------------------------------------------- // event listener fuckery // -------------------------------------------------------------------------------------- @@ -1029,9 +839,8 @@ Exception(e, 'script error at addEventListener:') } VersionActions() - count = Load(cwith) // loads resources if (!url.includes('.pdf')) { ShowMenu() } - return count + cwith() } function VersionActions () { @@ -1046,21 +855,6 @@ // : Version action functions {{{ - function SetActivesAsJSON () { - if (!getVal('actives')) { - let res = [] - for (let i = 0; i < 100; i++) { - let a = getVal('Is' + i + 'Active') - if (a && data.Subjects[i]) { - res.push(data.Subjects[i].Name) - } - delVal('Is' + i + 'Active') - } - delVal('Is-1Active') - setVal('actives', JSON.stringify(res)) - } - } - function FreshStart () { var firstRun = getVal('firstRun') // if the current run is the frst if (firstRun === undefined || firstRun === true) { @@ -1075,134 +869,6 @@ // : }}} - function ReadNetDB (cwith) { - function NewXMLHttpRequest () { - const url = serverAdress + 'data.json' - xmlhttpRequest({ - method: 'GET', - synchronous: true, - url: url, - onload: function (response) { - NLoad(response.responseText, cwith) - }, - onerror: function () { - NLoad(undefined, cwith) // server down - } - }) - } - try { - Log('Sending XMLHTTP Request...') - return NewXMLHttpRequest() - } catch (e) { - Exception(e, 'script error at reading online database:') - } - } - - function Load (cwith) { - let skipLoad = getVal('skipLoad') - - if (skipLoad) { - cwith(-2, -2) - return -1 - } - - ReadNetDB(cwith) - } - - function LoadMOTD (resource) { - try { - motd = resource.motd - } catch (e) { - Log('Error loading motd :c') - Log(e) - } - } - - function LoadVersion (resource) { - try { - lastestVersion = resource.version - } catch (e) { - Log('Error loading version :c') - Log(e) - } - } - - // loading stuff - function NLoad (resource, cwith) { - assert(resource) - - var count = -1 - var subjCount = 0 - try { - var d = {} - try { - d = JSON.parse(resource) - } catch (e) { - Log('Old data, trying with old methods....') - Log('Couldt parse data!') - Log(e) - ShowMessage({ - m: texts.couldntLoadData, - isSimple: true - }, undefined, ShowHelp) - } - data = new QuestionDB(getVal, setVal, delVal) - var rt = [] - var allCount = -1 - LoadMOTD(d) - LoadVersion(d) - - let actives = [] - try { - actives = JSON.parse(getVal('actives')) - if (!Array.isArray(actives)) { - throw new Error('not an array') - } - } catch (e) { - Log('Unable to parse active subjects!') - setVal('actives', '[]') - actives = [] - } - - for (let i = 0; i < d.Subjects.length; i++) { - let s = new Subject(d.Subjects[i].Name) - s.setIndex(i) - let isActive = actives.includes(d.Subjects[i].Name) - if (isActive) { - s.setActive(true) - var j = 0 - for (j = 0; j < d.Subjects[i].Questions.length; j++) { - var currQ = d.Subjects[i].Questions[j] - s.AddQuestion(new Question(currQ.Q, currQ.A, currQ.data)) - } - rt.push({ - name: d.Subjects[i].Name, - count: j - }) - allCount += j - subjCount++ - } - data.AddSubject(s) - } - - count = allCount + 1 // couse starting with -1 to show errors - } catch (e) { - Exception(e, 'script error at loading:') - count = -1 // returns -1 if error - } - cwith(count, subjCount) - } - - function AlertOnNoQuestion () { - try { - document.getElementById('HelperMenuButton').style.background = 'yellow' - } catch (e) { - Log('Unable to get helper menu button') - } - } - - // : }}} - // : UI handling {{{ function HandleUI (url, count, subjCount) { // FIXME: normal string building with localisation :/ @@ -1231,23 +897,24 @@ timeout = undefined } greetMsg += count + ' kérdés és ' + subjCount + ' tárgy betöltve. (click for help).' - if (data.length > 0) { - var toAdd = [] - for (var i = 0; i < data.length; i++) { - if (data.GetIfActive(i)) { - toAdd.push(data.Subjects[i].Name + ' (' + data.Subjects[i].length + ')') - } - } - if (toAdd.length !== 0) { - greetMsg += '\nAktív tárgyak: ' + toAdd.join(', ') + '.' - } else { - AlertOnNoQuestion() - greetMsg += '\nNincs aktív tárgyad. Menüből válassz ki eggyet!' - timeout = undefined - } - } else { - greetMsg += ' nem elérhető a szerver. Katt a helpért!' - } + // TODO + // if (data.length > 0) { + // var toAdd = [] + // for (var i = 0; i < data.length; i++) { + // if (data.GetIfActive(i)) { + // toAdd.push(data.Subjects[i].Name + ' (' + data.Subjects[i].length + ')') + // } + // } + // if (toAdd.length !== 0) { + // greetMsg += '\nAktív tárgyak: ' + toAdd.join(', ') + '.' + // } else { + // AlertOnNoQuestion() + // greetMsg += '\nNincs aktív tárgyad. Menüből válassz ki eggyet!' + // timeout = undefined + // } + // } else { + // greetMsg += ' nem elérhető a szerver. Katt a helpért!' + // } } // new version, nothing loaded if (newVersion && !loaded) { // -------------------------------------------------------------------------------------------------------------- @@ -1295,15 +962,30 @@ var questions = q.q var imgNodes = q.imgnodes // ------------------------------------------------------------------------------------------------------ - var answers = [] - questions.forEach((x, j) => { + let promises = [] + // TODO: test multiple promises + questions.forEach((x) => { let question = SUtils.EmptyOrWhiteSpace(x) ? '' : SUtils.RemoveUnnecesarySpaces(x) // simplifying question - var result = data.Search(question, GetImageDataFromImgNodes(imgNodes)) - var r = PrepareAnswers(result, j) - if (r !== undefined) { answers.push(r) } - HighLightAnswer(result, j) // highlights the answer for the current result + promises.push(GetXHRQuestionAnswer({ + q: question, + data: GetImageDataFromImgNodes(imgNodes), + subj: '' // MPM.GetCurrentSubjectName() // TODO: set subj to '' if no result as backup plan + })) + }) + + // TODO: check answer order! + Promise.all(promises).then((res) => { + console.log('All data recieved', res) // TODO: delete + 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) }) - ShowAnswers(answers, q.q) } function PrepareAnswers (result, j) { @@ -1317,9 +999,10 @@ 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.HasImage()) { - msg += '\n\nKépek fenti válaszok sorrendjében: ' + result[k].q.data.images.join(', ') // if it has image part, adding that too - } + // TODO + // if (result[k].q.HasImage()) { + // msg += '\n\nKépek fenti válaszok sorrendjében: ' + result[k].q.data.images.join(', ') // if it has image part, adding that too + // } allMessages.push({ m: msg, p: result[k].match @@ -1352,7 +1035,7 @@ // : Quiz saving {{{ function HandleResults (url) { - var d = SaveQuiz(GetQuiz(), data) // saves the quiz questions and answers + var d = SaveQuiz(GetQuiz()) // saves the quiz questions and answers if (d) { ShowSaveQuizDialog(d.addedQ, d.allQ, d.allOutput, d.output, d.sendSuccess, d.sentData) } } @@ -1366,7 +1049,7 @@ if (!sendSuccess) { msg += ' Nem sikerült kérdéseket elküldeni szervernek. Ha gondolod utánanézhetsz.' } else { msg += 'Az új kérdések elküldve.' } } else { msg = 'A kérdőívben nincsen új kérdés. Ha mégis le akarod menteni klikk ide.' - if (!data) { msg += ' Lehet azért, mert nincs kérdés betöltve.' } + // TODO if (!data) { msg += ' Lehet azért, mert nincs kérdés betöltve.' } } // showing a message wit the click event, and the generated page ShowMessage({ @@ -1386,19 +1069,6 @@ }) } - function SearchSameQuestion (questionData, quiz, i) { - var r = questionData.Search(quiz[i]) - - let count = 0 - r.forEach((item) => { - if (item.match > minResultMatchPercent) { - count++ - } - }) - - return count === 0 ? -1 : count - } - // 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 @@ -1444,7 +1114,7 @@ } // saves the current quiz. questionData contains the active subjects questions - function SaveQuiz (quiz, questionData) { + function SaveQuiz (quiz) { try { if (quiz.length === 0) { throw new Error('quiz length is zero!') @@ -1459,15 +1129,12 @@ var toAdd = '' // this will be added to some variable depending on if its already in the database toAdd += '?' + SUtils.RemoveUnnecesarySpaces(quiz[i].Q) + '\n' // adding quiz question toAdd += '!' + SUtils.RemoveUnnecesarySpaces(quiz[i].A) + '\n' // adding quiz answer - if (quiz[i].HasImage()) { - let imgString = quiz[i].data.images.join(', ') - toAdd += '>' + imgString + '\n' // adding quiz image if there is any - } - if (SearchSameQuestion(questionData, quiz, i) === -1) { - output += toAdd // adding to output - newQuestions.push(quiz[i]) - addedQ++ - } + // TODO: hasimage + // if (quiz[i].HasImage()) { + // let imgString = quiz[i].data.images.join(', ') + // toAdd += '>' + imgString + '\n' // adding quiz image if there is any + // } + // TODO: search same question removed allOutput += toAdd // adding to all allQ++ } @@ -2005,146 +1672,146 @@ width: '98%' }) - if (data && data.length > 0) { - let grouped = data.Subjects.reduce((res, s) => { - let sName = s.getSubjNameWithoutYear() - if (sName) { - if (!res[sName]) { - res[sName] = [] - } - res[sName].push(s) - } else { - res.others.push(s) - } - return res - }, { - others: [] - }) + // if (data && data.length > 0) { + // let grouped = data.Subjects.reduce((res, s) => { + // let sName = s.getSubjNameWithoutYear() + // if (sName) { + // if (!res[sName]) { + // res[sName] = [] + // } + // res[sName].push(s) + // } else { + // res.others.push(s) + // } + // return res + // }, { + // others: [] + // }) - const ordered = {} - Object.keys(grouped).sort().forEach((key) => { - ordered[key] = grouped[key] - }) + // const ordered = {} + // Object.keys(grouped).sort().forEach((key) => { + // ordered[key] = grouped[key] + // }) - grouped = ordered + // grouped = ordered - let collapsibles = [] + // let collapsibles = [] - // -------------------------------------------------------------------------------- - let searchBar = CreateNodeWithText(subjTable, '', 'input') - SetStyle(searchBar, { - backgroundColor: '#222d32', - color: '#ffffff', - width: '100%', - border: 'none' - }) - searchBar.placeholder = texts.search - searchBar.addEventListener('keyup', function (e) { - collapsibles.forEach((x) => { - if (x.innerText.toLowerCase().includes(this.value.toLowerCase())) { - x.style.display = '' - } else { - x.style.display = 'none' - } - }) - }) // adding click + // // -------------------------------------------------------------------------------- + // let searchBar = CreateNodeWithText(subjTable, '', 'input') + // SetStyle(searchBar, { + // backgroundColor: '#222d32', + // color: '#ffffff', + // width: '100%', + // border: 'none' + // }) + // searchBar.placeholder = texts.search + // searchBar.addEventListener('keyup', function (e) { + // collapsibles.forEach((x) => { + // if (x.innerText.toLowerCase().includes(this.value.toLowerCase())) { + // x.style.display = '' + // } else { + // x.style.display = 'none' + // } + // }) + // }) // adding click - Object.entries(grouped).forEach(([subjName, subjGroup], i) => { - let b = CreateNodeWithText(subjTable, subjName, 'div') - SetStyle(b, { - backgroundColor: '#222d32', - color: '#ffffff', - cursor: 'pointer', - padding: '5px', - width: '100%', - border: 'none', - textAlign: 'left', - outline: 'none' - }) - b.setAttribute('id', 'subjectGroup' + i) - collapsibles.push(b) + // Object.entries(grouped).forEach(([subjName, subjGroup], i) => { + // let b = CreateNodeWithText(subjTable, subjName, 'div') + // SetStyle(b, { + // backgroundColor: '#222d32', + // color: '#ffffff', + // cursor: 'pointer', + // padding: '5px', + // width: '100%', + // border: 'none', + // textAlign: 'left', + // outline: 'none' + // }) + // b.setAttribute('id', 'subjectGroup' + i) + // collapsibles.push(b) - let content = document.createElement('div') - SetStyle(content, { - padding: '0 18px', - overflow: 'hidden', - backgroundColor: '#222d32', - borderColor: '#212127', - borderStyle: 'solid', - borderWidth: '2px' - }) - content.addEventListener('click', function (e) { - e.stopPropagation() - }) + // let content = document.createElement('div') + // SetStyle(content, { + // padding: '0 18px', + // overflow: 'hidden', + // backgroundColor: '#222d32', + // borderColor: '#212127', + // borderStyle: 'solid', + // borderWidth: '2px' + // }) + // content.addEventListener('click', function (e) { + // e.stopPropagation() + // }) - let ifGroupActive = subjGroup.some((x) => { - return x.getIfActive() - }) - content.style.display = ifGroupActive ? 'block' : 'none' + // let ifGroupActive = subjGroup.some((x) => { + // return x.getIfActive() + // }) + // content.style.display = ifGroupActive ? 'block' : 'none' - b.appendChild(content) + // b.appendChild(content) - let tbl = document.createElement('table') - content.appendChild(tbl) - subjGroup.forEach((subj) => { - var row = tbl.insertRow() - let td = row.insertCell() - let text = subj.getYear() || subj.Name - if (subj.length !== 0) { text += ' [ ' + subj.length + 'db ]' } - CreateNodeWithText(td, text) + // let tbl = document.createElement('table') + // content.appendChild(tbl) + // subjGroup.forEach((subj) => { + // var row = tbl.insertRow() + // let td = row.insertCell() + // let text = subj.getYear() || subj.Name + // if (subj.length !== 0) { text += ' [ ' + subj.length + 'db ]' } + // CreateNodeWithText(td, text) - td = row.insertCell() - let checkbox = document.createElement('input') // new paragraph - checkbox.type = 'checkbox' - checkbox.style.background = 'white' - checkbox.style.margin = '5px 5px 5px 5px' // fancy margin - td.appendChild(checkbox) // adding text box to main td + // td = row.insertCell() + // let checkbox = document.createElement('input') // new paragraph + // checkbox.type = 'checkbox' + // checkbox.style.background = 'white' + // checkbox.style.margin = '5px 5px 5px 5px' // fancy margin + // td.appendChild(checkbox) // adding text box to main td - checkbox.checked = subj.active - let i = subj.getIndex() - checkbox.setAttribute('id', 'HelperTextNode' + i) - checkbox.addEventListener('click', function () { - var checked = document.getElementById('HelperTextNode' + i).checked - data.ChangeActive(subj.Name, checked) - }) // adding click - }) + // checkbox.checked = subj.active + // let i = subj.getIndex() + // checkbox.setAttribute('id', 'HelperTextNode' + i) + // checkbox.addEventListener('click', function () { + // var checked = document.getElementById('HelperTextNode' + i).checked + // data.ChangeActive(subj.Name, checked) + // }) // adding click + // }) - b.addEventListener('click', function (e) { - this.classList.toggle('active') - if (content.style.display === 'block') { - content.style.display = 'none' - } else { - content.style.display = 'block' - } - }) - }) + // b.addEventListener('click', function (e) { + // this.classList.toggle('active') + // if (content.style.display === 'block') { + // content.style.display = 'none' + // } else { + // content.style.display = 'block' + // } + // }) + // }) - var scrollDiv = document.createElement('div') - scrollDiv.style.width = '100%' - scrollDiv.style.height = window.innerHeight - (window.innerHeight * 0.4) + 'px' - scrollDiv.style.overflow = 'auto' + // var scrollDiv = document.createElement('div') + // scrollDiv.style.width = '100%' + // scrollDiv.style.height = window.innerHeight - (window.innerHeight * 0.4) + 'px' + // scrollDiv.style.overflow = 'auto' - scrollDiv.appendChild(subjTable) + // scrollDiv.appendChild(subjTable) - var subjtblrow = tbl.insertRow() - var subjtbltd = subjtblrow.insertCell() - subjtbltd.appendChild(scrollDiv) - } else { // if no data - var noDataRow = tbl.insertRow() - var noDataRowCell = noDataRow.insertCell() - let textBox + // var subjtblrow = tbl.insertRow() + // var subjtbltd = subjtblrow.insertCell() + // subjtbltd.appendChild(scrollDiv) + // } else { // if no data + // var noDataRow = tbl.insertRow() + // var noDataRowCell = noDataRow.insertCell() + // let textBox - if (getVal('skipLoad')) { - textBox = CreateNodeWithText(noDataRowCell, - texts.passiveModeActivePopupMenuText - ) - } else { - textBox = CreateNodeWithText(noDataRowCell, - texts.couldntLoadDataPopupMenuText - ) - } - textBox.style.margin = fiveMargin // fancy margin - } + // if (getVal('skipLoad')) { + // textBox = CreateNodeWithText(noDataRowCell, + // texts.passiveModeActivePopupMenuText + // ) + // } else { + // textBox = CreateNodeWithText(noDataRowCell, + // texts.couldntLoadDataPopupMenuText + // ) + // } + // textBox.style.margin = fiveMargin // fancy margin + // } // show splash tickbox ----------------------------------------------------------------------------------------------------------------------------- var splasTickboxRow = tbl.insertRow() @@ -2271,6 +1938,37 @@ return paragraphElement } + function GetXHRQuestionAnswer (question) { + return new Promise((resolve, reject) => { + let url = serverAdress + 'q?' + 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('&') + + xmlhttpRequest({ + method: 'GET', + url: url, + onload: function (response) { + try { + resolve(JSON.parse(response.responseText)) + } catch (e) { + reject(new Error('json parse error')) + } + }, + onerror: (e) => { + console.log('GET ERROR', e) + reject(new Error('get error')) + } + }) + }) + } + function SendXHRMessage (message) { var url = serverAdress + 'isAdding' xmlhttpRequest({