diff --git a/README.md b/README.md index 7c7de34..27ee895 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Install: Requires node.js. -`npm install express connect-busboy ejs express-layout querystring express` +`npm install` then diff --git a/actions.js b/actions.js deleted file mode 100644 index 74b2f7f..0000000 --- a/actions.js +++ /dev/null @@ -1,533 +0,0 @@ -/* ---------------------------------------------------------------------------- -Question Server - GitLab: - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - ------------------------------------------------------------------------- */ - -module.exports = { - ProcessIncomingRequest: ProcessIncomingRequest, - CheckData: CheckData, - NLoad: NLoad, - LoadJSON: LoadJSON, - ProcessQA: ProcessQA -}; - -var staticFile = "public/data/static"; -var manFile = "public/man.html"; -var dataFile = "public/data.json"; -const recDataFile = "stats/recdata"; -const versionFile = "public/version"; -const motdFile = "public/motd"; -const qaFile = "public/qa"; - -var logger = require('./logger.js'); -var utils = require('./utils.js'); - -class Question { - constructor(q, a, i) { - this.Q = q; - this.A = a; - this.I = i; - } - toString() { - var r = "?" + this.Q + "\n!" + this.A; - if (this.I) - r += "\n>" + this.I; - return r; - } - HasQuestion() { - return this.Q != undefined; - } - HasAnswer() { - return this.A != undefined; - } - HasImage() { - return this.I != undefined; - } - IsComplete() { - return this.HasQuestion() && this.HasAnswer(); - } - Compare(q2, i) { - if (typeof q2 == 'string') { - var qmatchpercent = Question.CompareString(this.Q, q2); - - if (i == undefined || i.length == 0) - return qmatchpercent; - else { - if (this.HasImage()) { - const imatchpercent = this.HasImage() ? Question.CompareString(this.I.join(" "), i.join(" ")) : - 0; - return (qmatchpercent + imatchpercent) / 2; - } else { - qmatchpercent -= 30; - if (qmatchpercent < 0) - return 0; - else - return qmatchpercent; - } - } - } else { - const qmatchpercent = Question.CompareString(this.Q, q2.Q); - const amatchpercent = Question.CompareString(this.A, q2.A); - if (this.I != undefined) { - const imatchpercent = this.I == undefined ? Question.CompareString(this.I.join(" "), q2.I.join( - " ")) : 0; - return (qmatchpercent + amatchpercent + imatchpercent) / 3; - } else { - return (qmatchpercent + amatchpercent) / 2; - } - } - } - static CompareString(s1, s2) { - //if (s1 == undefined || s2 == undefined) - // return 0; - s1 = SimplifyStringForComparison(s1).split(" "); - s2 = SimplifyStringForComparison(s2).split(" "); - var match = 0; - for (var i = 0; i < s1.length; i++) - if (s2.includes(s1[i])) - match++; - var percent = Math.round(((match / s1.length) * 100).toFixed(2)); // matched words percent - var lengthDifference = Math.abs(s2.length - s1.length); - percent -= lengthDifference * 3; - if (percent < 0) - percent = 0; - return percent; - } -} - -class Subject { - constructor(n) { - this.Name = n; - this.Questions = []; - } - get length() { - return this.Questions.length; - } - AddQuestion(q) { - this.Questions.push(q); - } - Search(q, img) { - var r = []; - for (var i = 0; i < this.length; i++) { - let percent = this.Questions[i].Compare(q, img); - if (percent > minMatchAmmount) - r.push({ - q: this.Questions[i], - match: percent - }); - } - - for (var 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() { - this.Subjects = []; - } - get length() { - return this.Subjects.length; - } - get activeIndexes() { - var r = []; - for (var i = 0; i < this.length; i++) { - if (GM_getValue("Is" + i + "Active")) { - r.push(i); - } - } - return r; - } - GetIfActive(ind) { - return GM_getValue("Is" + ind + "Active"); - } - ChangeActive(i, value) { - GM_setValue("Is" + i + "Active", !!value); - } - AddQuestion(subj, q) { - 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, img) { - var r = []; - for (var i = 0; i < this.length; i++) - if (this.GetIfActive(i)) - r = r.concat(this.Subjects[i].Search(q, img)); - - for (var 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) { - 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"); - } -} - - -function Process(d, file) { - try { - logger.Log("File: " + file); - if (d.data.split("\n").length > 1) { - var oldFile = utils.ReadFile(file); - var newFile = oldFile + "\n"; - if (d.data[0] == '+') - newFile += d.data; - else - newFile += "+" + d.data; - - var newRes = CheckData(newFile); - var oldRes = CheckData(oldFile); - - if (oldRes.count > 0) - logger.Log("\t\told public result: " + oldRes.count, logger.GetColor("blue")); - else - logger.Log("\t\told public NLOD error, " + oldRes.log, logger.GetColor("redbg"), true); - - if (newRes.count > 0) - logger.Log("\t\tnew file result: " + newRes.count, logger.GetColor("blue")); - else - logger.Log("\t\tnew file NLOD error, " + newRes.log, logger.GetColor("redbg"), true); - - utils.WriteFile(newFile, file); - logger.Log("\t\tNew data written to: " + file); - - return newRes.count - oldRes.count; - } else - logger.Log("\t\tNo new data"); - - - } catch (e) { - Beep(); - logger.Log("\tError at processing data! File: " + file, logger.GetColor("redbg")); - logger.Log(e.toString(), logger.GetColor("redbg")); - } - return -1; -} - -function ProcessIncomingRequest(data) { - - if (data == undefined) { - logger.Log("\tRecieved data is undefined!", logger.GetColor("redbg")); - return; - } - - try { - let towrite = logger.GetDateString() + "\n"; - towrite += "------------------------------------------------------------------------------\n"; - towrite += data - towrite += "\n------------------------------------------------------------------------------\n"; - utils.AppendToFile(towrite, recDataFile); - } catch (e) { - logger.log("Error writing recieved data."); - } - - try { - var d = JSON.parse(data); - var dfile = utils.ReadFile(dataFile); - var data = LoadJSON(dfile); - var allQuestions = []; - for ( var i = 0; i < d.allData.length; i++){ - allQuestions.push(new Question(d.allData[i].Q, d.allData[i].A, d.allData[i].I)); - } - var questions = []; - for ( var i = 0; i < d.data.length; i++){ - let q = new Question(d.data[i].Q, d.data[i].A, d.data[i].I); - questions.push(q); - data.AddQuestion(d.subj, q); - } - - logger.Log("\t" + d.subj); - var msg = "All / new count: " + allQuestions.length + " / " + questions.length; - if (d.version != undefined) - msg += ". Version: " + d.version; - var color = logger.GetColor("green"); - - try { - data.version = utils.ReadFile(versionFile); - data.motd = utils.ReadFile(motdFile); - } catch (e) { - Log("MOTD/Version writing/reading error!"); - } - - if (data != undefined && d.data.length > 0){ - utils.WriteBackup(); - utils.WriteFile(JSON.stringify(data), dataFile); - msg += " - Data file written!"; - var color = logger.GetColor("blue"); - } - logger.Log("\t" + msg, color); - - } catch (e) { - logger.Log("Couldnt parse JSON data, trying old format...", logger.GetColor("redbg")); - var d = SetupData(data); - var qcount = -1; - try { - var splitted = d.alldata.split("\n"); - var count = 0; - for (var i = 0; i < splitted.length; i++) - if (splitted[i][0] == '?') - count ++; - qcount = count; - } catch (e) {console.log("Error :c"); console.log(e);} - - logger.Log("\tProcessing data: " + d.subj + " (" + d.type + "), count: " + qcount, logger.GetColor("green")); - if (d.subj == undefined){ - logger.Log(JSON.stringify(d), logger.GetColor("red")); - return; - } - - var newItems = 0; - - var newStatItems = Process(d, staticFile); - - PrintNewCount(d, newStatItems, staticFile); - } -} - -function PrintNewCount(d, newItems, file) { - if (newItems > 0) { - var count = 0; - var splitted = d.alldata.split("\n"); - for (var i = 0; i < splitted.length; i++) - if (splitted[i].startsWith("?")) - count++; - logger.Log("\t" + file + " All / New: " + count + " / " + newItems, logger.GetColor("cyan")); - } -} - -function SetupData(data) { - var pdata = data.split("<#>"); - if (pdata.length <= 0){ - logger.Log("Data length is zero !", logger.GetColor("redbg")); - throw "No data recieved!"; - } - - var d = {}; // parsed data - for (var i = 0; i < pdata.length; i++) { - var td = pdata[i].split("<=>"); - if (td.length == 2) - d[td[0]] = td[1]; - else { - logger.Log("Invalid parameter!", logger.GetColor("redbg")); - throw "Invalid parameter recieved!"; - } - } - return d; -} - -function CheckData(data) { - try { - var presult = NLoad(data); - return presult; - } catch (e) { - logger.Log("Load error, " + e.toString(), logger.GetColor("redbg"), true); - return { - count: -1, - log: [e.toString()] - }; - } -} - -function NLoad(resource) { - var resultLog = []; - var count = -1; - var allCount = 0; - if (resource == undefined) - throw "A megadott adat undefined!"; - resource = resource.split("\n"); // splitting by enters - if (resource.length == 1) - throw "A megadott adat nem sorokból áll!"; - var data = []; // initializing data declared at the begining - - function AddNewSubj(name) // adds a new subject to the data - { - data.push({ - "questions": [] - }); // ads aa new object, with an empty array in it - GetCurrSubj().name = name; // sets the name for it - GetCurrSubj().count = 0; // setting count to default - // setting if its active only if its undefined, otherwise previous user setting shouldt be overwritten - } - - function AddItem() // adds an item to the last subjects questions - { - GetCurrSubj().count++; - allCount++; // incrementing all count - GetCurrSubj().questions.push({}); // adding a new empty object to the last item in the data - } - - function GetLastItem() // returns the last item of the last item of data - { - var q = GetCurrSubj().questions.length; // questions length - return GetCurrSubj().questions[q - 1]; - } - - function GetCurrSubj() { - return data[data.length - 1]; - } - - // ? : question - // ! : answer - // > : image JSON data - // + : subject name - - // checking for name - for (var j = 0; j < resource.length; j++) // goes through resources - { - if (resource[j][0] == '+') // if there is a name identifier - { - break; // breaks, couse there will be a name - } - if (resource[j][0] == '?' || resource[j][0] == '!' || resource[j][0] == '>') // if it begins with another identifier: - { - AddNewSubj("NONAME"); // there is no name (for the first question at least), so setting it noname. - break; - } - // else it does nothing, continues to check - } - var jumped = 0; // the amount of lines it processed - for (var i = 0; i < resource.length; i += jumped) // gouing through the resource - { - jumped = 0; // resetting it to 0 - var currRawDataQ = resource[i]; // current question - var currRawDataA = resource[i + 1]; // current answer - var currRawDataI = resource[i + 2]; // current image - if (currRawDataQ != undefined && currRawDataQ[0] == '?' && currRawDataA != undefined && - currRawDataA[0] == '!') // if the current line is ? and the next is ! its a data - { - AddItem(); - GetLastItem().q = currRawDataQ.substr(1); - GetLastItem().a = currRawDataA.substr(1); - jumped += 2; - if (currRawDataI != undefined && currRawDataI[0] == '>') { - GetLastItem().i = currRawDataI.substr(1); - jumped++; - } - } else if (currRawDataQ[0] == '+') // if its a new subject - { - AddNewSubj(currRawDataQ.substr(1)); - jumped++; - } else { - // this should be invalid question order - resultLog.push("Warning @ line " + i + ":" + currRawDataQ + " " + currRawDataA + " " + - currRawDataI); - jumped++; - } - } // end of parsing all data - return { - count: allCount, - log: resultLog - }; -} - -// loading stuff -function LoadJSON(resource) { - var count = -1; - try { - var d = JSON.parse(resource); - var r = new QuestionDB(); - var rt = []; - var allCount = -1; - - for (var i = 0; i < d.Subjects.length; i++) { - let s = new Subject(d.Subjects[i].Name); - 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.I)); - } - rt.push({ - name: d.Subjects[i].Name, - count: j - }); - allCount += j; - r.AddSubject(s); - } - return r; - } catch (e) { - logger.Log("Error loading sutff", logger.GetColor("redbg"), true); - } -} - -function ProcessQA() { - if (!utils.FileExists(qaFile)) - utils.WriteFile("", qaFile); - - let a = utils.ReadFile(qaFile).split("\n"); - let r = []; - let ind = 0; - for (let i = 0; i < a.length; i++) { - if ( a[i] == "#") - ind ++; - else { - if (r[ind] == undefined) - r[ind] = {}; - - if (r[ind].q == undefined) { - r[ind].q = a[i]; - } else { - if (r[ind].a == undefined) - r[ind].a = []; - - r[ind].a.push(a[i]); - } - } - } - - return r; -} diff --git a/changedataversion.js b/changedataversion.js deleted file mode 100644 index 2b5052a..0000000 --- a/changedataversion.js +++ /dev/null @@ -1,32 +0,0 @@ -const utils = require('./utils.js'); -const dataFile = "public/data.json"; -const versionFile = "public/version"; - -var p = GetParams(); -if (p.length <= 0) { - console.log("no params!"); - process.exit(0); -} - - -var param = p.join(" "); - -console.log("param: " + param); - -var d = utils.ReadFile(dataFile); -var parsed = JSON.parse(d); - -console.log("Old version:"); -console.log(parsed.version); - -parsed.version = param; - -console.log("New version:"); -console.log(parsed.version); - -utils.WriteFile(JSON.stringify(parsed), dataFile); -utils.WriteFile(parsed.version, versionFile); - -function GetParams() { - return process.argv.splice(2); -} diff --git a/logger.js b/logger.js deleted file mode 100644 index bd848f0..0000000 --- a/logger.js +++ /dev/null @@ -1,104 +0,0 @@ -/* ---------------------------------------------------------------------------- - - Question Server - GitLab: - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - ------------------------------------------------------------------------- */ - -module.exports = { - GetDateString: GetDateString, - Log: Log, - GetColor: GetColor, - LogReq: LogReq -}; - -const DELIM = "|"; - -var utils = require('./utils.js'); -const nlogFile = "stats/nlogs"; -const logFile = "/nlogs/nlogs"; -const locLogFile = "stats/logs"; -const allLogFile = "/nlogs/log"; - -function GetDateString() { - var m = new Date(); - return m.getFullYear() + "/" + - ("0" + (m.getMonth() + 1)).slice(-2) + "/" + - ("0" + m.getDate()).slice(-2) + " " + - ("0" + m.getHours()).slice(-2) + ":" + - ("0" + m.getMinutes()).slice(-2) + ":" + - ("0" + m.getSeconds()).slice(-2); -} - -function Log(s, c, b) { - if (c != undefined) - console.log(c, GetDateString() + DELIM + s); - else - console.log(GetDateString() + DELIM + s); - - if (b) - utils.Beep(); - - utils.AppendToFile(GetDateString() + DELIM + s, nlogFile); - utils.AppendToFile(GetDateString() + DELIM + s, logFile); -} - -function LogReq(req, toFile, sc) { - try { - var ip = req.headers['cf-connecting-ip'] || req.connection.remoteAddress; - var logEntry = ip + DELIM + req.hostname + DELIM + req.headers['user-agent'] + - DELIM + req.method + DELIM; - - logEntry += req.url; - - if (sc != undefined && sc == 404) - logEntry += DELIM + sc; - var color = GetColor("green"); - - if (req.url.toLowerCase().includes("isadding")) - color = GetColor("yellow"); - if (!toFile) { - Log(logEntry, color); - } else { - var defLogs = GetDateString() + DELIM + logEntry; - var extraLogs = "\n\t" + JSON.stringify(req.headers) + "\n\t" + JSON.stringify(req.body) + "\n"; - - utils.AppendToFile(defLogs, locLogFile); - utils.AppendToFile(defLogs, allLogFile); - } - - } catch (e) { - console.log(e); - Log("Error at logging lol", GetColor("redbg"), true); - } -} - -function GetColor(c) { - if (c == "redbg") - return '\x1b[41m%s\x1b[0m'; - if (c == "bluebg") - return '\x1b[44m%s\x1b[0m'; - if (c == "red") - return '\x1b[31m%s\x1b[0m'; - if (c == "green") - return '\x1b[32m%s\x1b[0m'; - if (c == "yellow") - return '\x1b[33m%s\x1b[0m'; - if (c == "blue") - return '\x1b[34m%s\x1b[0m'; - if (c == "cyan") - return '\x1b[36m%s\x1b[0m'; -} diff --git a/merger.js b/merger.js deleted file mode 100644 index 2833705..0000000 --- a/merger.js +++ /dev/null @@ -1,452 +0,0 @@ -/* ---------------------------------------------------------------------------- - - Question Server question file merger - GitLab: - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - ------------------------------------------------------------------------- */ - -// TODO: handle flags -// join json datas, or raw datas -// or something else - -const minMatchAmmount = 55; -const minResultMatchPercent = 99; -const lengthDiffMultiplier = 10; - -class Question { - constructor(q, a, i) { - this.Q = q; - this.A = a; - this.I = i; - } - toString() { - var r = "?" + this.Q + "\n!" + this.A; - if (this.I) - r += "\n>" + this.I; - return r; - } - HasQuestion() { - return this.Q != undefined; - } - HasAnswer() { - return this.A != undefined; - } - HasImage() { - return this.I != undefined; - } - IsComplete() { - return this.HasQuestion() && this.HasAnswer(); - } - // TODO: TEST DIS - Compare(q2, i) { - if (typeof q2 == 'string') { - var qmatchpercent = Question.CompareString(this.Q, q2); - - if (i == undefined || i.length == 0) - return qmatchpercent; - else { - if (this.HasImage()) { - const imatchpercent = this.HasImage() ? Question.CompareString(this.I.join(" "), i.join(" ")) : - 0; - return (qmatchpercent + imatchpercent) / 2; - } else { - qmatchpercent -= 30; - if (qmatchpercent < 0) - return 0; - else - return qmatchpercent; - } - } - } else { - const qmatchpercent = Question.CompareString(this.Q, q2.Q); - const amatchpercent = Question.CompareString(this.A, q2.A); - if (this.I != undefined) { - const imatchpercent = this.I == undefined ? Question.CompareString(this.I.join(" "), q2.I.join( - " ")) : 0; - return (qmatchpercent + amatchpercent + imatchpercent) / 3; - } else { - return (qmatchpercent + amatchpercent) / 2; - } - } - } - static CompareString(s1, s2) { - s1 = SimplifyStringForComparison(s1).split(" "); - s2 = SimplifyStringForComparison(s2).split(" "); - var match = 0; - for (var i = 0; i < s1.length; i++) - if (s2.includes(s1[i])) - match++; - var percent = Math.round(((match / s1.length) * 100).toFixed(2)); // matched words percent - var lengthDifference = Math.abs(s2.length - s1.length); - percent -= lengthDifference * lengthDiffMultiplier; - if (percent < 0) - percent = 0; - return percent; - } -} - -class Subject { - constructor(n) { - this.Name = n; - this.Questions = []; - } - get length() { - return this.Questions.length; - } - AddQuestion(q) { - this.Questions.push(q); - } - 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() { - this.Subjects = []; - } - get length() { - return this.Subjects.length; - } - AddQuestion(subj, q) { - 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); - } - } - AddSubject(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"); - } -} -var utils = require('./utils.js'); -var actions = require('./actions.js'); - -Main(); - -function Main() { - console.clear(); - const params = GetParams(); - console.log(params); - var dbs = []; - - - for (var i = 0; i < params.length; i++) { - PrintLN(); - console.log(params[i] + ": "); - try { - dbs.push(ParseJSONData(utils.ReadFile(params[i]))); - console.log("JSON data added"); - } catch (e) { - console.log(e); - console.log("Trying with old format..."); - dbs.push(ReadData(utils.ReadFile(params[i])).result); - } - } - PrintLN(); - - dbs.forEach((item) => { - PrintDB(item); - }); - - var olds = []; - if (dbs.length == 1) { - for ( let i = 0; i < dbs[0].length; i++) - olds.push(dbs[0].Subjects[i].length); - } - - console.log("Parsed data count: " + dbs.length); - PrintLN(); - - console.log("Merging databases..."); - var db = MergeDatabases(dbs); - - console.log("Removing duplicates..."); - var r = RemoveDuplicates(db); - - console.log("RESULT:"); - PrintDB(r, olds); - - utils.WriteFile(JSON.stringify(r), "newData"); - console.log("File written!"); -} - -function PrintLN() { - console.log("------------------------------------------------------"); -} - -function PrintDB(r, olds) { - console.log("Data subject count: " + r.length); - var maxLength = 0; - for (var i = 0; i < r.length; i++) { - if (maxLength < r.Subjects[i].Name.length) - maxLength = r.Subjects[i].Name.length; - } - - let qcount = 0; - - for (var i = 0; i < r.length; i++) { - let line = i; - if (line < 10) - line += ' '; - - line += ": "; - var currLength = line.length + maxLength + 4; - line += r.Subjects[i].Name; - - while (line.length < currLength) { - if (i % 4 == 0) - line += "."; - else - line += " "; - } - - if (olds && olds.length > 0) { - // TODO: check if correct row! should be now, but well... - if (olds[i] < 10) - line += " "; - if (olds[i] < 100) - line += " "; - - line += olds[i]; - line += " -> "; - } - - if (r.Subjects[i].length < 10) - line += " "; - if (r.Subjects[i].length < 100) - line += " "; - - line += r.Subjects[i].length; - qcount += r.Subjects[i].length; - - line += " db"; - - console.log(line); - } - - console.log("Total questions: " + qcount); - - PrintLN(); -} - -function GetParams() { - return process.argv.splice(2); -} - -function ParseJSONData(data) { - var d = JSON.parse(data); - var r = new QuestionDB(); - var rt = []; - - for (var i = 0; i < d.Subjects.length; i++) { - let s = new Subject(d.Subjects[i].Name); - 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.I)); - } - rt.push({ - name: d.Subjects[i].Name, - count: j - }); - r.AddSubject(s); - } - return r; -} - -function MergeDatabases(dbs) { - var db = new QuestionDB(); - for (var i = 0; i < dbs.length; i++) - for (var j = 0; j < dbs[i].length; j++) - db.AddSubject(dbs[i].Subjects[j]); - return db; -} - -/* - * Returns a question database from the given data. - * Parameter should be raw read file in string with "\n"-s - * TODO: ??? -s are not listed as errors, tho works correctly - * */ -function ReadData(data) { - - const d = data.split("\n"); - const r = new QuestionDB(); - var logs = []; - var currSubj = ""; // the current subjects name - var ExpectedIdentifier = ['+', '?']; - let currQuestion = new Question(); - - var i = -1; - while (i < d.length) { - let currIdentifier; - let skipped = 0; - do { - if (skipped >= 1) - logs.push(i + ": " + d[i]); - i++; - if (i >= d.length) { - if (currQuestion.IsComplete()) - r.AddQuestion(currSubj, currQuestion); - return { - result: r, - logs: logs - }; - } - currIdentifier = d[i][0]; - skipped++; - } while (!ExpectedIdentifier.includes(currIdentifier) && i < d.length); - - let currData = d[i].substring(1).trim(); - - if (currIdentifier == '+') { - if (currQuestion.IsComplete()) - r.AddQuestion(currSubj, currQuestion); - currQuestion = new Question(); - currSubj = currData; - ExpectedIdentifier = ['?']; - continue; - } - - if (currIdentifier == '?') { - if (currQuestion.IsComplete()) { - r.AddQuestion(currSubj, currQuestion); - currQuestion = new Question(); - } - // overwriting is allowed here, bcus: - // ?????!> - currQuestion.Q = currData; - ExpectedIdentifier = ['!', '?']; - continue; - } - - if (currIdentifier == '!') { - // if dont have question continue - if (!currQuestion.HasQuestion()) - throw "No question! (A)"; - // dont allow overwriting - // ?!!!! - if (!currQuestion.HasAnswer()) { - currData = currData.replace("A helyes válaszok: ", ""); - currData = currData.replace("A helyes válasz: ", ""); - - currQuestion.A = currData; - } - ExpectedIdentifier = ['?', '>', '+']; - continue; - } - - if (currIdentifier == '>') { - // if dont have question or answer continue - if (!currQuestion.HasQuestion()) - throw "No question! (I)"; - if (!currQuestion.HasAnswer()) - throw "No asnwer! (I)"; - // dont allow overwriting - // ?!>>> - if (!currQuestion.HasImage()) { - try { - currQuestion.I = JSON.parse(currData); - } catch (e) { - currQuestion.I = currData.split(','); - } - } - ExpectedIdentifier = ['?', '+']; - continue; - } - } - - return { - result: r, - logs: logs - }; -} - -function RemoveDuplicates(dataObj) { - for (var i = 0; i < dataObj.length; i++) - RemoveDuplFromSubject(dataObj.Subjects[i]); - return dataObj; -} - -function RemoveDuplFromSubject(subj) { - var cp = subj.Questions; - subj.Questions = []; - for (var i = 0; i < cp.length; i++) { - var j = 0; - // Only removes 100% match! - while (j < subj.length && cp[i].Compare(subj.Questions[j]) != 100) { - j++; - } - if (j < subj.length) { - //console.log("----------------------------------------------------------"); - //console.log(cp[i].toString()); - //console.log(" VS "); - //console.log(subj.Questions[j].toString()); - //console.log(cp[i].Compare(subj.Questions[j])); - //console.log(j); - //console.log("removed:"); - //console.log(subj.Questions.splice(j, 1).toString()); - //console.log("----------------------------------------------------------"); - } else { - subj.AddQuestion(cp[i]); - } - } -} - -function SimplifyStringForComparison(value) { - value = RemoveUnnecesarySpaces(value).toLowerCase(); - var removableChars = [",", ".", ":", "!"]; - for (var i = 0; i < removableChars.length; i++) { - var regex = new RegExp(removableChars[i], "g"); - value.replace(regex, ""); - } - return value; -} - -function RemoveUnnecesarySpaces(toremove) { - toremove = NormalizeSpaces(toremove); - while (toremove.includes(" ")) // while the text includes double spaces replaces all of them with a single one - { - toremove = toremove.replace(/ /g, " "); - } - return toremove.trim(); -} - -function NormalizeSpaces(input) { - return input.replace(/\s/g, ' '); -} diff --git a/modules/main.js b/modules/main.js new file mode 100644 index 0000000..831e0d5 --- /dev/null +++ b/modules/main.js @@ -0,0 +1,75 @@ +/* ---------------------------------------------------------------------------- + + Question Server + GitLab: + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- */ + +const express = require('express') +const bodyParser = require('body-parser') +const busboy = require('connect-busboy') +const app = express() + +const logger = require('../utils/logger.js') +// const utils = require('../utils/utils.js') +// const actions = require('../utils/actions.js') +const stat = require('../utils/stat.js') +stat.Load() + +app.set('view engine', 'ejs') +app.use(function (req, res, next) { + res.on('finish', function () { + logger.LogReq(req, true, res.statusCode) + if (res.statusCode !== 404) { stat.LogStat(req.url) } + }) + next() +}) +app.use(express.static('public')) +app.use(busboy({ + limits: { + fileSize: 10000 * 1024 * 1024 + } +})) +app.use(bodyParser.json()) +app.use(bodyParser.urlencoded({ + limit: '5mb', + extended: true +})) +app.use(bodyParser.json({ + limit: '5mb' +})) + +// -------------------------------------------------------------- + +app.get('/', function (req, res) { + // res.render() + res.end('henlo') +}) + +app.get('*', function (req, res) { + res.render('shared/404') + res.status(404) + // utils.AppendToFile(logger.GetDateString() + ": " + "404 GET", logFile); +}) + +app.post('*', function (req, res) { + res.status(404) + // utils.AppendToFile(logger.GetDateString() + ": " + "404 POST", logFile); +}) + +exports.app = app + +logger.Log('Main module started', logger.GetColor('yellow')) diff --git a/modules/qmining.js b/modules/qmining.js new file mode 100644 index 0000000..1ed14ca --- /dev/null +++ b/modules/qmining.js @@ -0,0 +1,226 @@ +/* ---------------------------------------------------------------------------- + + Question Server + GitLab: + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- */ + +const siteUrl = 'https://qmining.frylabs.net' // http(s)//asd.basd + +const express = require('express') +const bodyParser = require('body-parser') +const busboy = require('connect-busboy') +const fs = require('fs') +const app = express() +// const http = require('http') +// const https = require('https') + +const logger = require('../utils/logger.js') +const utils = require('../utils/utils.js') +const actions = require('../utils/actions.js') +const stat = require('../utils/stat.js') +stat.Load() + +const recivedFiles = 'public/recivedfiles' +const uloadFiles = 'public/f' +const staticFile = 'public/data/static' +const dataFile = 'public/data.json' +const msgFile = 'stats/msgs' + +app.set('view engine', 'ejs') +app.use(function (req, res, next) { + res.on('finish', function () { + logger.LogReq(req, true, res.statusCode) + if (res.statusCode !== 404) { stat.LogStat(req.url) } + }) + next() +}) +app.use(express.static('public')) +app.use(busboy({ + limits: { + fileSize: 10000 * 1024 * 1024 + } +})) +app.use(bodyParser.json()) +app.use(bodyParser.urlencoded({ + limit: '5mb', + extended: true +})) +app.use(bodyParser.json({ + limit: '5mb' +})) + +// -------------------------------------------------------------- + +app.get('/', function (req, res) { + // req.hostname + + res.render('qmining/main', { + siteurl: siteUrl, + qa: actions.ProcessQA() + }) + res.end() +}) + +app.get('/manual', function (req, res) { + res.render('qmining/man') + res.end() + logger.LogReq(req) +}) + +app.get('/public', function (req, res) { + var response = '@Plz update :)' + response += utils.ReadFile(staticFile) + res.write(response) + res.end() +}) + +app.get('/static', function (req, res) { + var response = '@Plz update :)' + response += utils.ReadFile(staticFile) + res.write(response) + res.end() +}) + +app.get('/legacy', function (req, res) { + var f = utils.ReadFile(dataFile) + var d = actions.LoadJSON(f) + let qcount = 0 + for (let i = 0; i < d.length; i++) { qcount += d.Subjects[i].length } + let scount = d.length + + res.render('qmining/alldata', { + data: d, + scount: scount, + qcount: qcount, + siteurl: siteUrl + }) + + logger.LogReq(req) +}) + +app.post('/postfeedback', function (req, res) { + res.redirect('back') + logger.Log('New feedback message', logger.GetColor('bluebg'), true) + utils.AppendToFile('\n\n' + logger.GetDateString() + ': ' + req.body.message_field, msgFile) +}) + +app.get('/postfeedback', function (req, res) { + // TODO: res.redirect("/"); or if needs this anyways, becouse /postfeedback post handler already + // redirects + res.render('qmining/main', { + sdata: utils.ReadFile(staticFile) + }) +}) + +app.post('/isAdding', function (req, res) { + res.end('OK') + logger.LogReq(req) + actions.ProcessIncomingRequest(req.body.datatoadd) + utils.WriteBackup() +}) + +app.get('/lred', function (req, res) { + res.redirect('/legacy') + res.end() + logger.LogReq(req) +}) + +app.get('/menuClick', function (req, res) { + res.redirect('/') + res.end() + logger.LogReq(req) +}) + +// all questions readable +app.get('/allqr', function (req, res) { + var f = utils.ReadFile(dataFile) + var d = actions.LoadJSON(f) + + res.render('qmining/allqr', { + d: d.toString().split('\n') + }) + logger.LogReq(req) +}) + +app.get('/greasy', function (req, res) { + res.redirect('https://greasyfork.org/en/scripts/38999-moodle-elearning-kmooc-test-help') + res.end() + logger.LogReq(req) +}) + +app.get('/scriptgit', function (req, res) { + res.redirect('https://gitlab.com/YourFriendlyNeighborhoodDealer/moodle-test-userscript') + res.end() + logger.LogReq(req) +}) + +app.get('/servergit', function (req, res) { + res.redirect('https://gitlab.com/YourFriendlyNeighborhoodDealer/question-node-server') + res.end() + logger.LogReq(req) +}) + +function UploadFile (req, res, path, next) { + var fstream + req.pipe(req.busboy) + req.busboy.on('file', function (fieldname, file, filename) { + logger.Log('Uploading: ' + filename, logger.GetColor('blue')) + + utils.CreatePath(path, true) + let d = new Date() + let fn = d.getHours() + '' + d.getMinutes() + '' + d.getSeconds() + '_' + filename + + fstream = fs.createWriteStream(path + '/' + fn) + file.pipe(fstream) + fstream.on('close', function () { + logger.Log('Upload Finished of ' + path + '/' + fn, logger.GetColor('blue')) + next(fn) + }) + fstream.on('error', function (err) { + console.log(err) + res.end('something bad happened :s') + }) + }) +} + +app.route('/fosuploader').post(function (req, res, next) { + UploadFile(req, res, uloadFiles, (fn) => { + res.redirect('/f/' + fn) + }) +}) + +app.route('/badtestsender').post(function (req, res, next) { + UploadFile(req, res, recivedFiles, (fn) => { + res.render('qmining/uploaded') + }) + logger.LogReq(req) +}) + +app.get('*', function (req, res) { + res.render('shared/404') + res.status(404) + // utils.AppendToFile(logger.GetDateString() + ": " + "404 GET", logFile); +}) + +app.post('*', function (req, res) { + res.status(404) + // utils.AppendToFile(logger.GetDateString() + ": " + "404 POST", logFile); +}) + +exports.app = app + +logger.Log('Qmining module started', logger.GetColor('yellow')) diff --git a/modules/sio.js b/modules/sio.js new file mode 100644 index 0000000..bd6fceb --- /dev/null +++ b/modules/sio.js @@ -0,0 +1,107 @@ +/* ---------------------------------------------------------------------------- + + Question Server + GitLab: + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- */ + +const express = require('express') +const bodyParser = require('body-parser') +const busboy = require('connect-busboy') +const fs = require('fs') +const app = express() +// const http = require('http') +// const https = require('https') + +const logger = require('../utils/logger.js') +const utils = require('../utils/utils.js') +const stat = require('../utils/stat.js') +stat.Load() + +const uloadFiles = './public/f' + +app.set('view engine', 'ejs') +app.use(function (req, res, next) { + res.on('finish', function () { + logger.LogReq(req, true, res.statusCode) + if (res.statusCode !== 404) { stat.LogStat(req.url) } + }) + next() +}) +app.use(express.static('public')) +app.use(busboy({ + limits: { + fileSize: 10000 * 1024 * 1024 + } +})) +app.use(bodyParser.json()) +app.use(bodyParser.urlencoded({ + limit: '5mb', + extended: true +})) +app.use(bodyParser.json({ + limit: '5mb' +})) + +// -------------------------------------------------------------- + +app.get('/', function (req, res) { + res.render('sio/uload') + res.end() +}) + +function UploadFile (req, res, path, next) { + var fstream + req.pipe(req.busboy) + req.busboy.on('file', function (fieldname, file, filename) { + logger.Log('Uploading: ' + filename, logger.GetColor('blue')) + + utils.CreatePath(path, true) + let d = new Date() + let fn = d.getHours() + '' + d.getMinutes() + '' + d.getSeconds() + '_' + filename + + fstream = fs.createWriteStream(path + '/' + fn) + file.pipe(fstream) + fstream.on('close', function () { + logger.Log('Upload Finished of ' + path + '/' + fn, logger.GetColor('blue')) + next(fn) + }) + fstream.on('error', function (err) { + console.log(err) + res.end('something bad happened :s') + }) + }) +} + +app.route('/fosuploader').post(function (req, res, next) { + UploadFile(req, res, uloadFiles, (fn) => { + res.redirect('/f/' + fn) + }) +}) +app.get('*', function (req, res) { + res.render('shared/404') + res.status(404) + // utils.AppendToFile(logger.GetDateString() + ": " + "404 GET", logFile); +}) + +app.post('*', function (req, res) { + res.status(404) + // utils.AppendToFile(logger.GetDateString() + ": " + "404 POST", logFile); +}) + +exports.app = app + +logger.Log('Sio module started', logger.GetColor('yellow')) diff --git a/modules/stuff.js b/modules/stuff.js new file mode 100644 index 0000000..1321594 --- /dev/null +++ b/modules/stuff.js @@ -0,0 +1,124 @@ +/* ---------------------------------------------------------------------------- + + Question Server + GitLab: + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- */ + +const express = require('express') +const bodyParser = require('body-parser') +const busboy = require('connect-busboy') +const fs = require('fs') +const app = express() + +const logger = require('../utils/logger.js') +// const utils = require('../utils/utils.js') +// const actions = require('../utils/actions.js') +const stat = require('../utils/stat.js') +stat.Load() + +const listedFiles = './public/files' + +app.set('view engine', 'ejs') +app.use(function (req, res, next) { + res.on('finish', function () { + logger.LogReq(req, true, res.statusCode) + if (res.statusCode !== 404) { stat.LogStat(req.url) } + }) + next() +}) +app.use(express.static('public')) +app.use(busboy({ + limits: { + fileSize: 10000 * 1024 * 1024 + } +})) +app.use(bodyParser.json()) +app.use(bodyParser.urlencoded({ + limit: '5mb', + extended: true +})) +app.use(bodyParser.json({ + limit: '5mb' +})) + +// -------------------------------------------------------------- + +app.get('/*', function (req, res) { + let curr = listedFiles + '/' + req.url.substring('/stuff/'.length, req.url.length).split('?')[0] + let relPath = curr.substring('./public/files'.length, curr.length) + + if (relPath[relPath.length - 1] !== '/') { relPath += '/' } + + let t = relPath.split('/') + let prevDir = '' + for (let i = 0; i < t.length - 2; i++) { prevDir += t[i] + '/' } + + // curr = curr.replace(/\//g, "/"); + // relPath = relPath.replace(/\//g, "/"); + + logger.LogReq(req) + + if (fs.lstatSync(curr).isDirectory()) { + if (curr[curr.length - 1] !== '/') { curr += '/' } + + let f = [] + + fs.readdirSync(curr).forEach((item) => { + if (item[0] !== '.') { + let res = { name: item } + let stats = fs.statSync(curr + '/' + item) + + let fileSizeInBytes = stats['size'] + res.size = Math.round(fileSizeInBytes / 1000000) + + res.path = relPath + if (res.path[res.path.length - 1] !== '/') { res.path += '/' } + res.path += item + + res.mtime = stats['mtime'].toLocaleString() + + f.push(res) + } + }) + + res.render('stuff/folders', { + folders: f, + dirname: relPath, + prevDir + }) + } else { + let fileStream = fs.createReadStream(curr) + fileStream.pipe(res) + } +}) + +// ----------------------------------------------------------------------------------------------- + +app.get('*', function (req, res) { + res.render('shared/404') + res.status(404) + // utils.AppendToFile(logger.GetDateString() + ": " + "404 GET", logFile); +}) + +app.post('*', function (req, res) { + res.status(404) + // utils.AppendToFile(logger.GetDateString() + ": " + "404 POST", logFile); +}) + +exports.app = app + +logger.Log('Stuff module started', logger.GetColor('yellow')) diff --git a/motd.js b/motd.js deleted file mode 100644 index 6a8bcc9..0000000 --- a/motd.js +++ /dev/null @@ -1,24 +0,0 @@ -const utils = require('./utils.js'); -const dataFile = "public/data.json"; -const versionFile = "public/version"; -const motdFile = "public/motd"; - -var p = GetParams(); -if (p.length <= 0) { - console.log("no params!"); - process.exit(0); -} - - -var param = p.join(" "); -console.log("param: " + param); -var d = utils.ReadFile(dataFile); -var parsed = JSON.parse(d); -parsed.motd = param; -utils.WriteFile(JSON.stringify(parsed), dataFile); - -utils.WriteFile(parsed.motd, motdFile); - -function GetParams() { - return process.argv.splice(2); -} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3471a4b --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "node-ejs", + "main": "server.js", + "dependencies": { + "connect-busboy": "0.0.2", + "ejs": "^1.0.0", + "express": "^4.6.1", + "express-ejs-layouts": "^1.1.0", + "vhost": "^3.0.2" + } +} diff --git a/server.js b/server.js index 536957b..5b61971 100644 --- a/server.js +++ b/server.js @@ -18,314 +18,47 @@ ------------------------------------------------------------------------- */ -const startHTTPS = true -const siteUrl = 'https://qmining.tk' // http(s)//asd.basd -const ircURL = 'https://kiwiirc.com/nextclient/irc.sub.fm/#qmining' - const express = require('express') -const bodyParser = require('body-parser') -const busboy = require('connect-busboy') -const fs = require('fs') -const app = express() -const http = require('http') -const https = require('https') +const vhost = require('vhost') +const logger = require('./utils/logger.js') -const logger = require('./logger.js') -const utils = require('./utils.js') -const actions = require('./actions.js') -const stat = require('./stat.js') - -const listedFiles = './public/files' -const recivedFiles = 'public/recivedfiles' -const uloadFiles = 'public/f' -const staticFile = 'public/data/static' -const dataFile = 'public/data.json' -const msgFile = 'stats/msgs' - -// https://certbot.eff.org/ -const privkeyFile = '/etc/letsencrypt/live/qmining.tk/privkey.pem' -const fullchainFile = '/etc/letsencrypt/live/qmining.tk/fullchain.pem' -const chainFile = '/etc/letsencrypt/live/qmining.tk/chain.pem' - -var certsLoaded = false -if (startHTTPS && utils.FileExists(privkeyFile) && utils.FileExists(fullchainFile) && utils.FileExists( - chainFile)) { - try { - const key = fs.readFileSync(privkeyFile, 'utf8') - const cert = fs.readFileSync(fullchainFile, 'utf8') - const ca = fs.readFileSync(chainFile, 'utf8') - var certs = { - key: key, - cert: cert, - ca: ca - } - certsLoaded = true - } catch (e) { - logger.Log('Error loading cert files!', logger.GetColor('redbg')) - } -} +const qmining = require('./modules/qmining.js').app +const main = require('./modules/main.js').app +const sio = require('./modules/sio.js').app +const stuff = require('./modules/stuff.js').app const port = 8080 -const httpsPort = 8443 -app.set('view engine', 'ejs') -app.use(function (req, res, next) { - res.on('finish', function () { - logger.LogReq(req, true, res.statusCode) - if (res.statusCode !== 404) { stat.LogStat(req.url) } - }) - next() -}) -app.use(express.static('public')) -app.use(busboy({ - limits: { - fileSize: 10000 * 1024 * 1024 - } -})) -app.use(bodyParser.json()) -app.use(bodyParser.urlencoded({ - limit: '5mb', - extended: true -})) -app.use(bodyParser.json({ - limit: '5mb' -})) +// // https://certbot.eff.org/ +// const privkeyFile = '/etc/letsencrypt/live/qmining.tk/privkey.pem' +// const fullchainFile = '/etc/letsencrypt/live/qmining.tk/fullchain.pem' +// const chainFile = '/etc/letsencrypt/live/qmining.tk/chain.pem' +// +// var certsLoaded = false +// if (startHTTPS && utils.FileExists(privkeyFile) && utils.FileExists(fullchainFile) && utils.FileExists( +// chainFile)) { +// try { +// const key = fs.readFileSync(privkeyFile, 'utf8') +// const cert = fs.readFileSync(fullchainFile, 'utf8') +// const ca = fs.readFileSync(chainFile, 'utf8') +// var certs = { +// key: key, +// cert: cert, +// ca: ca +// } +// certsLoaded = true +// } catch (e) { +// logger.Log('Error loading cert files!', logger.GetColor('redbg')) +// } +// } -// -------------------------------------------------------------- +express() + .use(vhost('qmining.frylabs.net', qmining)) + .use(vhost('sio.frylabs.net', sio)) + .use(vhost('stuff.frylabs.net', stuff)) + .use(vhost('frylabs.net', main)) + .use(vhost('qmining.tk', qmining)) + .listen(port) -app.get('/', function (req, res) { - // req.hostname - - res.render('main', { - siteurl: siteUrl, - qa: actions.ProcessQA() - }) - res.end() -}) - -app.get('/sio', function (req, res) { - res.render('uload') - res.end() -}) - -app.get('/manual', function (req, res) { - res.render('man') - res.end() - logger.LogReq(req) -}) - -app.get('/public', function (req, res) { - var response = '@Plz update :)' - response += utils.ReadFile(staticFile) - res.write(response) - res.end() -}) - -app.get('/static', function (req, res) { - var response = '@Plz update :)' - response += utils.ReadFile(staticFile) - res.write(response) - res.end() -}) - -app.get('/legacy', function (req, res) { - var f = utils.ReadFile(dataFile) - var d = actions.LoadJSON(f) - let qcount = 0 - for (let i = 0; i < d.length; i++) { qcount += d.Subjects[i].length } - let scount = d.length - - res.render('alldata', { - data: d, - scount: scount, - qcount: qcount, - siteurl: siteUrl - }) - - logger.LogReq(req) -}) - -app.post('/postfeedback', function (req, res) { - res.redirect('back') - logger.Log('New feedback message', logger.GetColor('bluebg'), true) - utils.AppendToFile('\n\n' + logger.GetDateString() + ': ' + req.body.message_field, msgFile) -}) - -app.get('/postfeedback', function (req, res) { - // TODO: res.redirect("/"); or if needs this anyways, becouse /postfeedback post handler already - // redirects - res.render('main', { - sdata: utils.ReadFile(staticFile) - }) -}) - -app.post('/isAdding', function (req, res) { - res.end('OK') - logger.LogReq(req) - actions.ProcessIncomingRequest(req.body.datatoadd) - utils.WriteBackup() -}) - -app.get('/lred', function (req, res) { - res.redirect('/legacy') - res.end() - logger.LogReq(req) -}) - -app.get('/menuClick', function (req, res) { - res.redirect('/') - res.end() - logger.LogReq(req) -}) - -app.get('/irc', function (req, res) { - res.redirect(ircURL) - res.end() - logger.LogReq(req) -}) - -// all questions readable -app.get('/allqr', function (req, res) { - var f = utils.ReadFile(dataFile) - var d = actions.LoadJSON(f) - - res.render('allqr', { - d: d.toString().split('\n') - }) - logger.LogReq(req) -}) - -app.get('/greasy', function (req, res) { - res.redirect('https://greasyfork.org/en/scripts/38999-moodle-elearning-kmooc-test-help') - res.end() - logger.LogReq(req) -}) - -app.get('/scriptgit', function (req, res) { - res.redirect('https://gitlab.com/YourFriendlyNeighborhoodDealer/moodle-test-userscript') - res.end() - logger.LogReq(req) -}) - -app.get('/servergit', function (req, res) { - res.redirect('https://gitlab.com/YourFriendlyNeighborhoodDealer/question-node-server') - res.end() - logger.LogReq(req) -}) - -function UploadFile (req, res, path, next) { - var fstream - req.pipe(req.busboy) - req.busboy.on('file', function (fieldname, file, filename) { - logger.Log('Uploading: ' + filename, logger.GetColor('blue')) - - utils.CreatePath(path, true) - let d = new Date() - let fn = d.getHours() + '' + d.getMinutes() + '' + d.getSeconds() + '_' + filename - - fstream = fs.createWriteStream(path + '/' + fn) - file.pipe(fstream) - fstream.on('close', function () { - logger.Log('Upload Finished of ' + path + '/' + fn, logger.GetColor('blue')) - next(fn) - }) - fstream.on('error', function (err) { - console.log(err) - res.end('something bad happened :s') - }) - }) -} - -app.route('/fosuploader').post(function (req, res, next) { - UploadFile(req, res, uloadFiles, (fn) => { - res.redirect('/f/' + fn) - }) -}) - -app.route('/badtestsender').post(function (req, res, next) { - UploadFile(req, res, recivedFiles, (fn) => { - res.render('uploaded') - }) - logger.LogReq(req) -}) - -// ----------------------------------------------------------------------------------------------- - -app.get('/stuff*', function (req, res) { - let curr = listedFiles + '/' + req.url.substring('/stuff/'.length, req.url.length).split('?')[0] - let relPath = curr.substring('./public/files'.length, curr.length) - - if (relPath[relPath.length - 1] !== '/') { relPath += '/' } - - let t = relPath.split('/') - let prevDir = '' - for (let i = 0; i < t.length - 2; i++) { prevDir += t[i] + '/' } - - // curr = curr.replace(/\//g, "/"); - // relPath = relPath.replace(/\//g, "/"); - - logger.LogReq(req) - - if (fs.lstatSync(curr).isDirectory()) { - if (curr[curr.length - 1] !== '/') { curr += '/' } - - let f = [] - - fs.readdirSync(curr).forEach((item) => { - if (item[0] !== '.') { - let res = { name: item } - let stats = fs.statSync(curr + '/' + item) - - let fileSizeInBytes = stats['size'] - res.size = Math.round(fileSizeInBytes / 1000000) - - res.path = relPath - if (res.path[res.path.length - 1] !== '/') { res.path += '/' } - res.path += item - - res.mtime = stats['mtime'].toLocaleString() - - f.push(res) - } - }) - - res.render('folders', { - folders: f, - dirname: relPath, - prevDir - }) - } else { - let fileStream = fs.createReadStream(curr) - fileStream.pipe(res) - } -}) - -// ----------------------------------------------------------------------------------------------- - -app.get('*', function (req, res) { - res.render('404') - res.status(404) - // utils.AppendToFile(logger.GetDateString() + ": " + "404 GET", logFile); -}) - -app.post('*', function (req, res) { - res.status(404) - // utils.AppendToFile(logger.GetDateString() + ": " + "404 POST", logFile); -}) - -var msg = '' -stat.Load() - -const httpServer = http.createServer(app) -httpServer.listen(port) -msg += 'Server listening on port ' + port + ' (http)' - -if (startHTTPS && certsLoaded) { - const httpsServer = https.createServer(certs, app) - httpsServer.listen(httpsPort) - msg += ', and ' + httpsPort + ' (https)...' -} else { - logger.Log('Cert files does not exists, starting http only!', logger.GetColor('redbg')) -} -logger.Log(msg, logger.GetColor('yellow')) logger.Log('Node version: ' + process.version) +logger.Log('Listening on port: ' + port) diff --git a/stat.js b/stat.js deleted file mode 100644 index 96bc53c..0000000 --- a/stat.js +++ /dev/null @@ -1,112 +0,0 @@ -/* ---------------------------------------------------------------------------- - - Question Server - GitLab: - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - - ------------------------------------------------------------------------- */ - -module.exports = { - LogStat: LogStat, - Load: Load -}; - -var utils = require('./utils.js'); -var logger = require('./logger.js'); - -const statFile = "stats/stats"; -const vStatFile = "stats/vstats"; -const writeInterval = 10; - -var data = {}; -var vData = {}; -var writes = 0; - -function Load() { - try { - var prevData = utils.ReadFile(statFile); - data = JSON.parse(prevData); - } catch (e) { - logger.Log("Error at loading logs! (@ first run its normal)", logger.GetColor("redbg")); - console.log(e); - } - - try { - var prevVData = utils.ReadFile(vStatFile); - vData = JSON.parse(prevVData); - } catch (e) { - logger.Log("Error at loading visit logs! (@ first run its normal)", logger.GetColor("redbg")); - console.log(e); - } -} - -function LogStat(url) { - Inc(url); - AddVisitStat(url); - Save(); -} - -function Inc(value) { - if (value.startsWith("/?")) - value = "/"; - if (data[value] == undefined) - data[value] = 0; - data[value]++; -} - -function AddVisitStat(name) { - var m = new Date(); - const now = m.getFullYear() + "/" + - ("0" + (m.getMonth() + 1)).slice(-2) + "/" + - ("0" + m.getDate()).slice(-2); - if (vData[now] == undefined) - vData[now] = {}; - if (vData[now][name] == undefined) - vData[now][name] = 0; - vData[now][name]++; -} - -function AddVisitStat(name) { - var m = new Date(); - const now = m.getFullYear() + "/" + - ("0" + (m.getMonth() + 1)).slice(-2) + "/" + - ("0" + m.getDate()).slice(-2); - if (vData[now] == undefined) - vData[now] = {}; - if (vData[now][name] == undefined) - vData[now][name] = 0; - vData[now][name]++; -} - -function Save() { - writes++; - if (writes == writeInterval) { - try { - utils.WriteFile(JSON.stringify(data), statFile); - // logger.Log("Stats wrote."); - } catch (e) { - logger.Log("Error at writing logs!", logger.GetColor("redbg")); - console.log(e); - } - try { - utils.WriteFile(JSON.stringify(vData), vStatFile); - // logger.Log("Stats wrote."); - } catch (e) { - logger.Log("Error at writing visit logs!", logger.GetColor("redbg")); - console.log(e); - } - writes = 0; - } -} diff --git a/utils.js b/utils.js deleted file mode 100644 index e3f736b..0000000 --- a/utils.js +++ /dev/null @@ -1,109 +0,0 @@ -module.exports = { - ReadFile: ReadFile, - WriteFile: WriteFile, - writeFileAsync: WriteFileAsync, - AppendToFile: AppendToFile, - Beep: Beep, - WriteBackup: WriteBackup, - FileExists: FileExists, - CreatePath: CreatePath, - GetAllFilesFromFolder: GetAllFilesFromFolder -}; - -var fs = require('fs'); - -var logger = require('./logger.js'); - -const recievedFile = "stats/recieved"; -const manFile = "public/man.html"; -const logFile = "stats/logs"; -const dataFile = "public/data.json"; - -function ReadFile(name) { - if (!FileExists(name)) - throw "No such file: " + name; - return fs.readFileSync(name, "utf8"); -} - -function FileExists(path) { - return fs.existsSync(path); -} - -function CreatePath(path, onlyPath) { - if (FileExists(path)) - return; - - var p = path.split("/"); - var currDir = p[0]; - for (var i = 1; i < p.length; i++) { - if (currDir != "" && !fs.existsSync(currDir)) { - try { - fs.mkdirSync(currDir); - } catch (e) { console.log("Failed to make " + currDir + " directory... "); } - } - currDir += "/" + p[i]; - } - if (onlyPath == undefined || onlyPath == false) - fs.writeFileSync(path, ""); - else - fs.mkdirSync(path); - -} - -function WriteFile(content, path) { - CreatePath(path); - fs.writeFileSync(path, content); -} - -function WriteFileAsync(content, path) { - CreatePath(path); - fs.writeFile(path, content, function(err) { - if (err) { - logger.Log("Error writing file: " + path + " (sync)", logger.GetColor("redbg")); - } - }); -} - -function AppendToFile(data, file) { - CreatePath(file); - fs.appendFile(file, "\n" + data, function(err) { - if (err) - logger.Log("Error writing log file: " + file + " (sync)", logger.GetColor("redbg")); - }); -} - -function Beep() { - try { - process.stdout.write('\x07'); - } catch (e) { - console.log("error beepin"); - } -} - -function WriteBackup() { - try { - WriteFileAsync(ReadFile(dataFile), 'public/backs/data_' + new Date().toString()); - } catch (e) { - logger.Log("Error backing up data json file!", logger.GetColor("redbg")); - console.log(e); - } -} - -function GetAllFilesFromFolder(dir) { - - var results = []; - - fs.readdirSync(dir).forEach(function(file) { - - file = dir + '/' + file; - var stat = fs.statSync(file); - - if (stat && stat.isDirectory()) { - results = results.concat(_getAllFilesFromFolder(file)); - } else results.push(file); - - }); - - return results; -} - diff --git a/utils/actions.js b/utils/actions.js new file mode 100644 index 0000000..e691c7c --- /dev/null +++ b/utils/actions.js @@ -0,0 +1,496 @@ +/* ---------------------------------------------------------------------------- +Question Server + GitLab: + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- */ + +module.exports = { + ProcessIncomingRequest: ProcessIncomingRequest, + CheckData: CheckData, + NLoad: NLoad, + LoadJSON: LoadJSON, + ProcessQA: ProcessQA +} + +const staticFile = './public/data/static' +const dataFile = './public/data.json' +const recDataFile = './stats/recdata' +const versionFile = './public/version' +const motdFile = './public/motd' +const qaFile = './public/qa' + +var logger = require('../utils/logger.js') +var utils = require('../utils/utils.js') + +class Question { + constructor (q, a, i) { + this.Q = q + this.A = a + this.I = i + } + toString () { + var r = '?' + this.Q + '\n!' + this.A + if (this.I) { r += '\n>' + this.I } + return r + } + HasQuestion () { + return this.Q !== undefined + } + HasAnswer () { + return this.A !== undefined + } + HasImage () { + return this.I !== undefined + } + IsComplete () { + return this.HasQuestion() && this.HasAnswer() + } + Compare (q2, i) { + if (typeof q2 === 'string') { + var qmatchpercent = Question.CompareString(this.Q, q2) + + if (i == undefined || i.length == 0) { return qmatchpercent } else { + if (this.HasImage()) { + const imatchpercent = this.HasImage() ? Question.CompareString(this.I.join(' '), i.join(' ')) + : 0 + return (qmatchpercent + imatchpercent) / 2 + } else { + qmatchpercent -= 30 + if (qmatchpercent < 0) { return 0 } else { return qmatchpercent } + } + } + } else { + const qmatchpercent = Question.CompareString(this.Q, q2.Q) + const amatchpercent = Question.CompareString(this.A, q2.A) + if (this.I !== undefined) { + const imatchpercent = this.I === undefined ? Question.CompareString(this.I.join(' '), q2.I.join( + ' ')) : 0 + return (qmatchpercent + amatchpercent + imatchpercent) / 3 + } else { + return (qmatchpercent + amatchpercent) / 2 + } + } + } + + // static CompareString (s1, s2) { + // // if (s1 == undefined || s2 == undefined) + // // return 0; + // s1 = SimplifyStringForComparison(s1).split(' ') + // s2 = SimplifyStringForComparison(s2).split(' ') + // var match = 0 + // for (var i = 0; i < s1.length; i++) { + // if (s2.includes(s1[i])) { match++ } + // } + // var percent = Math.round(((match / s1.length) * 100).toFixed(2)) // matched words percent + // var lengthDifference = Math.abs(s2.length - s1.length) + // percent -= lengthDifference * 3 + // if (percent < 0) { percent = 0 } + // return percent + // } +} + +class Subject { + constructor (n) { + this.Name = n + this.Questions = [] + } + get length () { + return this.Questions.length + } + AddQuestion (q) { + this.Questions.push(q) + } + Search (q, img) { + const minMatchAmmount = 90 + var r = [] + for (let i = 0; i < this.length; i++) { + let percent = this.Questions[i].Compare(q, img) + 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 () { + this.Subjects = [] + } + get length () { + return this.Subjects.length + } + // get activeIndexes () { + // var r = [] + // for (var i = 0; i < this.length; i++) { + // if (GM_getValue('Is' + i + 'Active')) { + // r.push(i) + // } + // } + // return r + // } + // GetIfActive (ind) { + // return GM_getValue('Is' + ind + 'Active') + // } + // ChangeActive (i, value) { + // GM_setValue('Is' + i + 'Active', !!value) + // } + AddQuestion (subj, q) { + 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, img) { + var r = [] + for (let i = 0; i < this.length; i++) { + if (this.GetIfActive(i)) { r = r.concat(this.Subjects[i].Search(q, img)) } + } + + 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) { + 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') + } +} + +function Process (d, file) { + try { + logger.Log('File: ' + file) + if (d.data.split('\n').length > 1) { + var oldFile = utils.ReadFile(file) + var newFile = oldFile + '\n' + if (d.data[0] === '+') { newFile += d.data } else { newFile += '+' + d.data } + + var newRes = CheckData(newFile) + var oldRes = CheckData(oldFile) + + if (oldRes.count > 0) { logger.Log('\t\told public result: ' + oldRes.count, logger.GetColor('blue')) } else { logger.Log('\t\told public NLOD error, ' + oldRes.log, logger.GetColor('redbg'), true) } + + if (newRes.count > 0) { logger.Log('\t\tnew file result: ' + newRes.count, logger.GetColor('blue')) } else { logger.Log('\t\tnew file NLOD error, ' + newRes.log, logger.GetColor('redbg'), true) } + + utils.WriteFile(newFile, file) + logger.Log('\t\tNew data written to: ' + file) + + return newRes.count - oldRes.count + } else { logger.Log('\t\tNo new data') } + } catch (e) { + logger.Log('\tError at processing data! File: ' + file, logger.GetColor('redbg')) + logger.Log(e.toString(), logger.GetColor('redbg')) + } + return -1 +} + +function ProcessIncomingRequest (data) { + if (data === undefined) { + logger.Log('\tRecieved data is undefined!', logger.GetColor('redbg')) + return + } + + try { + let towrite = logger.GetDateString() + '\n' + towrite += '------------------------------------------------------------------------------\n' + towrite += data + towrite += '\n------------------------------------------------------------------------------\n' + utils.AppendToFile(towrite, recDataFile) + } catch (e) { + logger.log('Error writing recieved data.') + } + + try { + var d = JSON.parse(data) + var dfile = utils.ReadFile(dataFile) + data = LoadJSON(dfile) + var allQuestions = [] + for (let i = 0; i < d.allData.length; i++) { + allQuestions.push(new Question(d.allData[i].Q, d.allData[i].A, d.allData[i].I)) + } + var questions = [] + for (let i = 0; i < d.data.length; i++) { + let q = new Question(d.data[i].Q, d.data[i].A, d.data[i].I) + questions.push(q) + data.AddQuestion(d.subj, q) + } + + logger.Log('\t' + d.subj) + var msg = 'All / new count: ' + allQuestions.length + ' / ' + questions.length + if (d.version !== undefined) { msg += '. Version: ' + d.version } + var color = logger.GetColor('green') + + try { + data.version = utils.ReadFile(versionFile) + data.motd = utils.ReadFile(motdFile) + } catch (e) { + logger.Log('MOTD/Version writing/reading error!') + } + + if (data !== undefined && d.data.length > 0) { + utils.WriteBackup() + utils.WriteFile(JSON.stringify(data), dataFile) + msg += ' - Data file written!' + color = logger.GetColor('blue') + } + logger.Log('\t' + msg, color) + } catch (e) { + logger.Log('Couldnt parse JSON data, trying old format...', logger.GetColor('redbg')) + d = SetupData(data) + var qcount = -1 + try { + var splitted = d.alldata.split('\n') + var count = 0 + for (var i = 0; i < splitted.length; i++) { + if (splitted[i][0] === '?') { count++ } + } + qcount = count + } catch (e) { console.log('Error :c'); console.log(e) } + + logger.Log('\tProcessing data: ' + d.subj + ' (' + d.type + '), count: ' + qcount, logger.GetColor('green')) + if (d.subj === undefined) { + logger.Log(JSON.stringify(d), logger.GetColor('red')) + return + } + + var newStatItems = Process(d, staticFile) + + PrintNewCount(d, newStatItems, staticFile) + } +} + +function PrintNewCount (d, newItems, file) { + if (newItems > 0) { + var count = 0 + var splitted = d.alldata.split('\n') + for (var i = 0; i < splitted.length; i++) { + if (splitted[i].startsWith('?')) { count++ } + } + logger.Log('\t' + file + ' All / New: ' + count + ' / ' + newItems, logger.GetColor('cyan')) + } +} + +function SetupData (data) { + var pdata = data.split('<#>') + if (pdata.length <= 0) { + logger.Log('Data length is zero !', logger.GetColor('redbg')) + throw 'No data recieved!' + } + + var d = {} // parsed data + for (var i = 0; i < pdata.length; i++) { + var td = pdata[i].split('<=>') + if (td.length === 2) { d[td[0]] = td[1] } else { + logger.Log('Invalid parameter!', logger.GetColor('redbg')) + throw 'Invalid parameter recieved!' + } + } + return d +} + +function CheckData (data) { + try { + var presult = NLoad(data) + return presult + } catch (e) { + logger.Log('Load error, ' + e.toString(), logger.GetColor('redbg'), true) + return { + count: -1, + log: [e.toString()] + } + } +} + +function NLoad (resource) { + var resultLog = [] + var allCount = 0 + if (resource === undefined) { throw 'A megadott adat undefined!' } + resource = resource.split('\n') // splitting by enters + if (resource.length === 1) { throw 'A megadott adat nem sorokból áll!' } + var data = [] // initializing data declared at the begining + + function AddNewSubj (name) { + data.push({ + 'questions': [] + }) // ads aa new object, with an empty array in it + GetCurrSubj().name = name // sets the name for it + GetCurrSubj().count = 0 // setting count to default + // setting if its active only if its undefined, otherwise previous user setting shouldt be overwritten + } + + function AddItem () // adds an item to the last subjects questions + { + GetCurrSubj().count++ + allCount++ // incrementing all count + GetCurrSubj().questions.push({}) // adding a new empty object to the last item in the data + } + + function GetLastItem () // returns the last item of the last item of data + { + var q = GetCurrSubj().questions.length // questions length + return GetCurrSubj().questions[q - 1] + } + + function GetCurrSubj () { + return data[data.length - 1] + } + + // ? : question + // ! : answer + // > : image JSON data + // + : subject name + + // checking for name + for (var j = 0; j < resource.length; j++) // goes through resources + { + if (resource[j][0] == '+') // if there is a name identifier + { + break // breaks, couse there will be a name + } + if (resource[j][0] == '?' || resource[j][0] == '!' || resource[j][0] == '>') // if it begins with another identifier: + { + AddNewSubj('NONAME') // there is no name (for the first question at least), so setting it noname. + break + } + // else it does nothing, continues to check + } + var jumped = 0 // the amount of lines it processed + for (var i = 0; i < resource.length; i += jumped) // gouing through the resource + { + jumped = 0 // resetting it to 0 + var currRawDataQ = resource[i] // current question + var currRawDataA = resource[i + 1] // current answer + var currRawDataI = resource[i + 2] // current image + if (currRawDataQ != undefined && currRawDataQ[0] == '?' && currRawDataA != undefined && + currRawDataA[0] == '!') // if the current line is ? and the next is ! its a data + { + AddItem() + GetLastItem().q = currRawDataQ.substr(1) + GetLastItem().a = currRawDataA.substr(1) + jumped += 2 + if (currRawDataI != undefined && currRawDataI[0] == '>') { + GetLastItem().i = currRawDataI.substr(1) + jumped++ + } + } else if (currRawDataQ[0] == '+') // if its a new subject + { + AddNewSubj(currRawDataQ.substr(1)) + jumped++ + } else { + // this should be invalid question order + resultLog.push('Warning @ line ' + i + ':' + currRawDataQ + ' ' + currRawDataA + ' ' + + currRawDataI) + jumped++ + } + } // end of parsing all data + return { + count: allCount, + log: resultLog + } +} + +// loading stuff +function LoadJSON (resource) { + var count = -1 + try { + var d = JSON.parse(resource) + var r = new QuestionDB() + var rt = [] + var allCount = -1 + + for (var i = 0; i < d.Subjects.length; i++) { + let s = new Subject(d.Subjects[i].Name) + 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.I)) + } + rt.push({ + name: d.Subjects[i].Name, + count: j + }) + allCount += j + r.AddSubject(s) + } + return r + } catch (e) { + logger.Log('Error loading sutff', logger.GetColor('redbg'), true) + } +} + +function ProcessQA () { + if (!utils.FileExists(qaFile)) { utils.WriteFile('', qaFile) } + + let a = utils.ReadFile(qaFile).split('\n') + let r = [] + let ind = 0 + for (let i = 0; i < a.length; i++) { + if (a[i] == '#') { ind++ } else { + if (r[ind] == undefined) { r[ind] = {} } + + if (r[ind].q == undefined) { + r[ind].q = a[i] + } else { + if (r[ind].a == undefined) { r[ind].a = [] } + + r[ind].a.push(a[i]) + } + } + } + + return r +} diff --git a/utils/changedataversion.js b/utils/changedataversion.js new file mode 100644 index 0000000..94582e2 --- /dev/null +++ b/utils/changedataversion.js @@ -0,0 +1,31 @@ +const utils = require('../utils/utils.js') +const dataFile = '../public/data.json' +const versionFile = '../public/version' + +var p = GetParams() +if (p.length <= 0) { + console.log('no params!') + process.exit(0) +} + +var param = p.join(' ') + +console.log('param: ' + param) + +var d = utils.ReadFile(dataFile) +var parsed = JSON.parse(d) + +console.log('Old version:') +console.log(parsed.version) + +parsed.version = param + +console.log('New version:') +console.log(parsed.version) + +utils.WriteFile(JSON.stringify(parsed), dataFile) +utils.WriteFile(parsed.version, versionFile) + +function GetParams () { + return process.argv.splice(2) +} diff --git a/utils/logger.js b/utils/logger.js new file mode 100644 index 0000000..e8f7bc2 --- /dev/null +++ b/utils/logger.js @@ -0,0 +1,90 @@ +/* ---------------------------------------------------------------------------- + + Question Server + GitLab: + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- */ + +module.exports = { + GetDateString: GetDateString, + Log: Log, + GetColor: GetColor, + LogReq: LogReq +} + +const DELIM = '|' + +var utils = require('../utils/utils.js') +const nlogFile = './stats/nlogs' +const locLogFile = './stats/logs' +const logFile = '/nlogs/nlogs' +const allLogFile = '/nlogs/log' + +function GetDateString () { + var m = new Date() + return m.getFullYear() + '/' + + ('0' + (m.getMonth() + 1)).slice(-2) + '/' + + ('0' + m.getDate()).slice(-2) + ' ' + + ('0' + m.getHours()).slice(-2) + ':' + + ('0' + m.getMinutes()).slice(-2) + ':' + + ('0' + m.getSeconds()).slice(-2) +} + +function Log (s, c, b) { + if (c != undefined) { console.log(c, GetDateString() + DELIM + s) } else { console.log(GetDateString() + DELIM + s) } + + if (b) { utils.Beep() } + + utils.AppendToFile(GetDateString() + DELIM + s, nlogFile) + utils.AppendToFile(GetDateString() + DELIM + s, logFile) +} + +function LogReq (req, toFile, sc) { + try { + var ip = req.headers['cf-connecting-ip'] || req.connection.remoteAddress + var logEntry = ip + DELIM + req.hostname + DELIM + req.headers['user-agent'] + + DELIM + req.method + DELIM + + logEntry += req.url + + if (sc != undefined && sc == 404) { logEntry += DELIM + sc } + var color = GetColor('green') + + if (req.url.toLowerCase().includes('isadding')) { color = GetColor('yellow') } + if (!toFile) { + Log(logEntry, color) + } else { + var defLogs = GetDateString() + DELIM + logEntry + var extraLogs = '\n\t' + JSON.stringify(req.headers) + '\n\t' + JSON.stringify(req.body) + '\n' + + utils.AppendToFile(defLogs, locLogFile) + utils.AppendToFile(defLogs, allLogFile) + } + } catch (e) { + console.log(e) + Log('Error at logging lol', GetColor('redbg'), true) + } +} + +function GetColor (c) { + if (c == 'redbg') { return '\x1b[41m%s\x1b[0m' } + if (c == 'bluebg') { return '\x1b[44m%s\x1b[0m' } + if (c == 'red') { return '\x1b[31m%s\x1b[0m' } + if (c == 'green') { return '\x1b[32m%s\x1b[0m' } + if (c == 'yellow') { return '\x1b[33m%s\x1b[0m' } + if (c == 'blue') { return '\x1b[34m%s\x1b[0m' } + if (c == 'cyan') { return '\x1b[36m%s\x1b[0m' } +} diff --git a/utils/merger.js b/utils/merger.js new file mode 100644 index 0000000..b3b0e07 --- /dev/null +++ b/utils/merger.js @@ -0,0 +1,420 @@ +/* ---------------------------------------------------------------------------- + + Question Server question file merger + GitLab: + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- */ + +// TODO: handle flags +// join json datas, or raw datas +// or something else + +const minMatchAmmount = 55 +const minResultMatchPercent = 99 +const lengthDiffMultiplier = 10 + +class Question { + constructor (q, a, i) { + this.Q = q + this.A = a + this.I = i + } + toString () { + var r = '?' + this.Q + '\n!' + this.A + if (this.I) { r += '\n>' + this.I } + return r + } + HasQuestion () { + return this.Q != undefined + } + HasAnswer () { + return this.A != undefined + } + HasImage () { + return this.I != undefined + } + IsComplete () { + return this.HasQuestion() && this.HasAnswer() + } + // TODO: TEST DIS + Compare (q2, i) { + if (typeof q2 === 'string') { + var qmatchpercent = Question.CompareString(this.Q, q2) + + if (i == undefined || i.length == 0) { return qmatchpercent } else { + if (this.HasImage()) { + const imatchpercent = this.HasImage() ? Question.CompareString(this.I.join(' '), i.join(' ')) + : 0 + return (qmatchpercent + imatchpercent) / 2 + } else { + qmatchpercent -= 30 + if (qmatchpercent < 0) { return 0 } else { return qmatchpercent } + } + } + } else { + const qmatchpercent = Question.CompareString(this.Q, q2.Q) + const amatchpercent = Question.CompareString(this.A, q2.A) + if (this.I != undefined) { + const imatchpercent = this.I == undefined ? Question.CompareString(this.I.join(' '), q2.I.join( + ' ')) : 0 + return (qmatchpercent + amatchpercent + imatchpercent) / 3 + } else { + return (qmatchpercent + amatchpercent) / 2 + } + } + } + static CompareString (s1, s2) { + s1 = SimplifyStringForComparison(s1).split(' ') + s2 = SimplifyStringForComparison(s2).split(' ') + var match = 0 + for (var i = 0; i < s1.length; i++) { + if (s2.includes(s1[i])) { match++ } + } + var percent = Math.round(((match / s1.length) * 100).toFixed(2)) // matched words percent + var lengthDifference = Math.abs(s2.length - s1.length) + percent -= lengthDifference * lengthDiffMultiplier + if (percent < 0) { percent = 0 } + return percent + } +} + +class Subject { + constructor (n) { + this.Name = n + this.Questions = [] + } + get length () { + return this.Questions.length + } + AddQuestion (q) { + this.Questions.push(q) + } + 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 () { + this.Subjects = [] + } + get length () { + return this.Subjects.length + } + AddQuestion (subj, q) { + 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) + } + } + AddSubject (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') + } +} +var utils = require('./utils.js') +var actions = require('./actions.js') + +Main() + +function Main () { + console.clear() + const params = GetParams() + console.log(params) + var dbs = [] + + for (var i = 0; i < params.length; i++) { + PrintLN() + console.log(params[i] + ': ') + try { + dbs.push(ParseJSONData(utils.ReadFile(params[i]))) + console.log('JSON data added') + } catch (e) { + console.log(e) + console.log('Trying with old format...') + dbs.push(ReadData(utils.ReadFile(params[i])).result) + } + } + PrintLN() + + dbs.forEach((item) => { + PrintDB(item) + }) + + var olds = [] + if (dbs.length == 1) { + for (let i = 0; i < dbs[0].length; i++) { olds.push(dbs[0].Subjects[i].length) } + } + + console.log('Parsed data count: ' + dbs.length) + PrintLN() + + console.log('Merging databases...') + var db = MergeDatabases(dbs) + + console.log('Removing duplicates...') + var r = RemoveDuplicates(db) + + console.log('RESULT:') + PrintDB(r, olds) + + utils.WriteFile(JSON.stringify(r), 'newData') + console.log('File written!') +} + +function PrintLN () { + console.log('------------------------------------------------------') +} + +function PrintDB (r, olds) { + console.log('Data subject count: ' + r.length) + var maxLength = 0 + for (var i = 0; i < r.length; i++) { + if (maxLength < r.Subjects[i].Name.length) { maxLength = r.Subjects[i].Name.length } + } + + let qcount = 0 + + for (var i = 0; i < r.length; i++) { + let line = i + if (line < 10) { line += ' ' } + + line += ': ' + var currLength = line.length + maxLength + 4 + line += r.Subjects[i].Name + + while (line.length < currLength) { + if (i % 4 == 0) { line += '.' } else { line += ' ' } + } + + if (olds && olds.length > 0) { + // TODO: check if correct row! should be now, but well... + if (olds[i] < 10) { line += ' ' } + if (olds[i] < 100) { line += ' ' } + + line += olds[i] + line += ' -> ' + } + + if (r.Subjects[i].length < 10) { line += ' ' } + if (r.Subjects[i].length < 100) { line += ' ' } + + line += r.Subjects[i].length + qcount += r.Subjects[i].length + + line += ' db' + + console.log(line) + } + + console.log('Total questions: ' + qcount) + + PrintLN() +} + +function GetParams () { + return process.argv.splice(2) +} + +function ParseJSONData (data) { + var d = JSON.parse(data) + var r = new QuestionDB() + var rt = [] + + for (var i = 0; i < d.Subjects.length; i++) { + let s = new Subject(d.Subjects[i].Name) + 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.I)) + } + rt.push({ + name: d.Subjects[i].Name, + count: j + }) + r.AddSubject(s) + } + return r +} + +function MergeDatabases (dbs) { + var db = new QuestionDB() + for (var i = 0; i < dbs.length; i++) { + for (var j = 0; j < dbs[i].length; j++) { db.AddSubject(dbs[i].Subjects[j]) } + } + return db +} + +/* + * Returns a question database from the given data. + * Parameter should be raw read file in string with "\n"-s + * TODO: ??? -s are not listed as errors, tho works correctly + * */ +function ReadData (data) { + const d = data.split('\n') + const r = new QuestionDB() + var logs = [] + var currSubj = '' // the current subjects name + var ExpectedIdentifier = ['+', '?'] + let currQuestion = new Question() + + var i = -1 + while (i < d.length) { + let currIdentifier + let skipped = 0 + do { + if (skipped >= 1) { logs.push(i + ': ' + d[i]) } + i++ + if (i >= d.length) { + if (currQuestion.IsComplete()) { r.AddQuestion(currSubj, currQuestion) } + return { + result: r, + logs: logs + } + } + currIdentifier = d[i][0] + skipped++ + } while (!ExpectedIdentifier.includes(currIdentifier) && i < d.length) + + let currData = d[i].substring(1).trim() + + if (currIdentifier == '+') { + if (currQuestion.IsComplete()) { r.AddQuestion(currSubj, currQuestion) } + currQuestion = new Question() + currSubj = currData + ExpectedIdentifier = ['?'] + continue + } + + if (currIdentifier == '?') { + if (currQuestion.IsComplete()) { + r.AddQuestion(currSubj, currQuestion) + currQuestion = new Question() + } + // overwriting is allowed here, bcus: + // ?????!> + currQuestion.Q = currData + ExpectedIdentifier = ['!', '?'] + continue + } + + if (currIdentifier == '!') { + // if dont have question continue + if (!currQuestion.HasQuestion()) { throw 'No question! (A)' } + // dont allow overwriting + // ?!!!! + if (!currQuestion.HasAnswer()) { + currData = currData.replace('A helyes válaszok: ', '') + currData = currData.replace('A helyes válasz: ', '') + + currQuestion.A = currData + } + ExpectedIdentifier = ['?', '>', '+'] + continue + } + + if (currIdentifier == '>') { + // if dont have question or answer continue + if (!currQuestion.HasQuestion()) { throw 'No question! (I)' } + if (!currQuestion.HasAnswer()) { throw 'No asnwer! (I)' } + // dont allow overwriting + // ?!>>> + if (!currQuestion.HasImage()) { + try { + currQuestion.I = JSON.parse(currData) + } catch (e) { + currQuestion.I = currData.split(',') + } + } + ExpectedIdentifier = ['?', '+'] + continue + } + } + + return { + result: r, + logs: logs + } +} + +function RemoveDuplicates (dataObj) { + for (var i = 0; i < dataObj.length; i++) { RemoveDuplFromSubject(dataObj.Subjects[i]) } + return dataObj +} + +function RemoveDuplFromSubject (subj) { + var cp = subj.Questions + subj.Questions = [] + for (var i = 0; i < cp.length; i++) { + var j = 0 + // Only removes 100% match! + while (j < subj.length && cp[i].Compare(subj.Questions[j]) != 100) { + j++ + } + if (j < subj.length) { + // console.log("----------------------------------------------------------"); + // console.log(cp[i].toString()); + // console.log(" VS "); + // console.log(subj.Questions[j].toString()); + // console.log(cp[i].Compare(subj.Questions[j])); + // console.log(j); + // console.log("removed:"); + // console.log(subj.Questions.splice(j, 1).toString()); + // console.log("----------------------------------------------------------"); + } else { + subj.AddQuestion(cp[i]) + } + } +} + +function SimplifyStringForComparison (value) { + value = RemoveUnnecesarySpaces(value).toLowerCase() + var removableChars = [',', '.', ':', '!'] + for (var i = 0; i < removableChars.length; i++) { + var regex = new RegExp(removableChars[i], 'g') + value.replace(regex, '') + } + return value +} + +function RemoveUnnecesarySpaces (toremove) { + toremove = NormalizeSpaces(toremove) + while (toremove.includes(' ')) // while the text includes double spaces replaces all of them with a single one + { + toremove = toremove.replace(/ {2}/g, ' ') + } + return toremove.trim() +} + +function NormalizeSpaces (input) { + return input.replace(/\s/g, ' ') +} diff --git a/utils/motd.js b/utils/motd.js new file mode 100644 index 0000000..6f50a6a --- /dev/null +++ b/utils/motd.js @@ -0,0 +1,23 @@ +const utils = require('../utils/utils.js') +const dataFile = '../public/data.json' +const versionFile = '../public/version' +const motdFile = '../public/motd' + +var p = GetParams() +if (p.length <= 0) { + console.log('no params!') + process.exit(0) +} + +var param = p.join(' ') +console.log('param: ' + param) +var d = utils.ReadFile(dataFile) +var parsed = JSON.parse(d) +parsed.motd = param +utils.WriteFile(JSON.stringify(parsed), dataFile) + +utils.WriteFile(parsed.motd, motdFile) + +function GetParams () { + return process.argv.splice(2) +} diff --git a/utils/stat.js b/utils/stat.js new file mode 100644 index 0000000..2539ec7 --- /dev/null +++ b/utils/stat.js @@ -0,0 +1,96 @@ +/* ---------------------------------------------------------------------------- + + Question Server + GitLab: + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- */ + +module.exports = { + LogStat: LogStat, + Load: Load +} + +var utils = require('../utils/utils.js') +var logger = require('../utils/logger.js') + +const statFile = 'stats/stats' +const vStatFile = 'stats/vstats' +const writeInterval = 10 + +var data = {} +var vData = {} +var writes = 0 + +function Load () { + try { + var prevData = utils.ReadFile(statFile) + data = JSON.parse(prevData) + } catch (e) { + logger.Log('Error at loading logs! (@ first run its normal)', logger.GetColor('redbg')) + console.log(e) + } + + try { + var prevVData = utils.ReadFile(vStatFile) + vData = JSON.parse(prevVData) + } catch (e) { + logger.Log('Error at loading visit logs! (@ first run its normal)', logger.GetColor('redbg')) + console.log(e) + } +} + +function LogStat (url) { + Inc(url) + AddVisitStat(url) + Save() +} + +function Inc (value) { + if (value.startsWith('/?')) { value = '/' } + if (data[value] == undefined) { data[value] = 0 } + data[value]++ +} + +function AddVisitStat (name) { + var m = new Date() + const now = m.getFullYear() + '/' + + ('0' + (m.getMonth() + 1)).slice(-2) + '/' + + ('0' + m.getDate()).slice(-2) + if (vData[now] == undefined) { vData[now] = {} } + if (vData[now][name] == undefined) { vData[now][name] = 0 } + vData[now][name]++ +} + +function Save () { + writes++ + if (writes == writeInterval) { + try { + utils.WriteFile(JSON.stringify(data), statFile) + // logger.Log("Stats wrote."); + } catch (e) { + logger.Log('Error at writing logs!', logger.GetColor('redbg')) + console.log(e) + } + try { + utils.WriteFile(JSON.stringify(vData), vStatFile) + // logger.Log("Stats wrote."); + } catch (e) { + logger.Log('Error at writing visit logs!', logger.GetColor('redbg')) + console.log(e) + } + writes = 0 + } +} diff --git a/utils/utils.js b/utils/utils.js new file mode 100644 index 0000000..86f5e78 --- /dev/null +++ b/utils/utils.js @@ -0,0 +1,98 @@ +module.exports = { + ReadFile: ReadFile, + WriteFile: WriteFile, + writeFileAsync: WriteFileAsync, + AppendToFile: AppendToFile, + Beep: Beep, + WriteBackup: WriteBackup, + FileExists: FileExists, + CreatePath: CreatePath, + GetAllFilesFromFolder: GetAllFilesFromFolder +} + +var fs = require('fs') + +var logger = require('../utils/logger.js') + +const recievedFile = './stats/recieved' +const manFile = './public/man.html' +const logFile = './stats/logs' +const dataFile = './public/data.json' + +function ReadFile (name) { + if (!FileExists(name)) { throw 'No such file: ' + name } + return fs.readFileSync(name, 'utf8') +} + +function FileExists (path) { + return fs.existsSync(path) +} + +function CreatePath (path, onlyPath) { + if (FileExists(path)) { return } + + var p = path.split('/') + var currDir = p[0] + for (var i = 1; i < p.length; i++) { + if (currDir != '' && !fs.existsSync(currDir)) { + try { + fs.mkdirSync(currDir) + } catch (e) { console.log('Failed to make ' + currDir + ' directory... ') } + } + currDir += '/' + p[i] + } + if (onlyPath == undefined || onlyPath == false) { fs.writeFileSync(path, '') } else { fs.mkdirSync(path) } +} + +function WriteFile (content, path) { + CreatePath(path) + fs.writeFileSync(path, content) +} + +function WriteFileAsync (content, path) { + CreatePath(path) + fs.writeFile(path, content, function (err) { + if (err) { + logger.Log('Error writing file: ' + path + ' (sync)', logger.GetColor('redbg')) + } + }) +} + +function AppendToFile (data, file) { + CreatePath(file) + fs.appendFile(file, '\n' + data, function (err) { + if (err) { logger.Log('Error writing log file: ' + file + ' (sync)', logger.GetColor('redbg')) } + }) +} + +function Beep () { + try { + process.stdout.write('\x07') + } catch (e) { + console.log('error beepin') + } +} + +function WriteBackup () { + try { + WriteFileAsync(ReadFile(dataFile), 'public/backs/data_' + new Date().toString()) + } catch (e) { + logger.Log('Error backing up data json file!', logger.GetColor('redbg')) + console.log(e) + } +} + +function GetAllFilesFromFolder (dir) { + var results = [] + + fs.readdirSync(dir).forEach(function (file) { + file = dir + '/' + file + var stat = fs.statSync(file) + + if (stat && stat.isDirectory()) { + results = results.concat(_getAllFilesFromFolder(file)) + } else results.push(file) + }) + + return results +} diff --git a/views/alldata.ejs b/views/qmining/alldata.ejs similarity index 100% rename from views/alldata.ejs rename to views/qmining/alldata.ejs diff --git a/views/allqr.ejs b/views/qmining/allqr.ejs similarity index 100% rename from views/allqr.ejs rename to views/qmining/allqr.ejs diff --git a/views/aludni.ejs b/views/qmining/aludni.ejs similarity index 100% rename from views/aludni.ejs rename to views/qmining/aludni.ejs diff --git a/views/b.ejs b/views/qmining/b.ejs similarity index 100% rename from views/b.ejs rename to views/qmining/b.ejs diff --git a/views/main.ejs b/views/qmining/main.ejs similarity index 100% rename from views/main.ejs rename to views/qmining/main.ejs diff --git a/views/man.ejs b/views/qmining/man.ejs similarity index 100% rename from views/man.ejs rename to views/qmining/man.ejs diff --git a/views/qa.ejs b/views/qmining/qa.ejs similarity index 100% rename from views/qa.ejs rename to views/qmining/qa.ejs diff --git a/views/uploaded.ejs b/views/qmining/uploaded.ejs similarity index 100% rename from views/uploaded.ejs rename to views/qmining/uploaded.ejs diff --git a/views/404.ejs b/views/shared/404.ejs similarity index 100% rename from views/404.ejs rename to views/shared/404.ejs diff --git a/views/uload.ejs b/views/sio/uload.ejs similarity index 100% rename from views/uload.ejs rename to views/sio/uload.ejs diff --git a/views/stuff/folders.ejs b/views/stuff/folders.ejs new file mode 100644 index 0000000..ea6b70f --- /dev/null +++ b/views/stuff/folders.ejs @@ -0,0 +1,96 @@ + + + + + + <%=dirname%> + + + +
+

+ <%=dirname%> +

+
+ > Up one level +

+ + + <% for (var i = 0; i < folders.length; i++) { %> + + + + <% } %> +
+ +
+ +