initial commit
This commit is contained in:
commit
7f1682c04f
|
@ -0,0 +1 @@
|
|||
node_modules
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,146 @@
|
|||
var express = require('express');
|
||||
var app = express();
|
||||
var cors = require('cors');
|
||||
var escape = require('escape-html')
|
||||
var bodyParser = require('body-parser');
|
||||
var sqlite3 = require('sqlite3').verbose();
|
||||
var db = new sqlite3.Database('db.sqlite', sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, function (err) {
|
||||
if(err) {
|
||||
console.log('error opening db:', err);
|
||||
} else {
|
||||
db.run("CREATE TABLE IF NOT EXISTS images (id INTEGER PRIMARY KEY, src TEXT, dataUri TEXT)");
|
||||
db.run("CREATE TABLE IF NOT EXISTS questions (id INTEGER PRIMARY KEY, updated DATETIME DEFAULT CURRENT_TIMESTAMP, text TEXT, explanation TEXT)");
|
||||
db.run("ALTER TABLE questions ADD COLUMN explanation TEXT", function (err) {});
|
||||
db.run("CREATE TABLE IF NOT EXISTS answers (id INTEGER PRIMARY KEY, aid INTEGER, qid INTEGER, text TEXT, correct INTEGER, checked INTEGER)");
|
||||
db.run("CREATE TABLE IF NOT EXISTS answering (id INTEGER PRIMARY KEY, created DATETIME DEFAULT CURRENT_TIMESTAMP, version TEXT, href TEXT)");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
app.use( bodyParser.json() );
|
||||
app.use(cors());
|
||||
app.set('view engine', 'pug');
|
||||
|
||||
|
||||
|
||||
|
||||
var corsOptions = {
|
||||
origin: "*",
|
||||
methods: "POST"
|
||||
};
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
db.all("SELECT q.id AS qid, q.text AS question, q.explanation AS explanation, i.dataUri AS image, T2.id AS aid, T2.text AS answer, T2.correct FROM questions q " +
|
||||
"LEFT JOIN (SELECT qid, MAX(aid) AS maxid FROM answers GROUP BY qid) AS T1 ON q.id = T1.qid " +
|
||||
"LEFT JOIN answers AS T2 ON T2.aid = T1.maxid AND T2.qid = q.id " +
|
||||
"LEFT JOIN images AS i ON i.id = q.id " +
|
||||
"ORDER BY T2.id DESC",
|
||||
function (err, rows) {
|
||||
//console.log('selected rows:', rows.length);
|
||||
if (err) {
|
||||
console.log('error:', err);
|
||||
res.sendStatus(500);
|
||||
}
|
||||
var prevQid, index = -1;
|
||||
var questions = rows.reduce(function(questions, row) {
|
||||
var a = { answer: row.answer, correct: row.correct };
|
||||
if (prevQid != row.qid) {
|
||||
questions[++index] = { id: row.qid, question: row.question, explanation: row.explanation, image: row.image, answers: [a] };
|
||||
} else {
|
||||
questions[index].answers.push(a);
|
||||
}
|
||||
prevQid = row.qid;
|
||||
return questions;
|
||||
}, []);
|
||||
console.log('total questions:', questions.length);
|
||||
res.render('index', { questions: questions });
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/explanation/:id', function (req, res) {
|
||||
var id = req.params.id;
|
||||
var data = req.body;
|
||||
if (id && data.explanation) {
|
||||
console.log('updating', id, 'with', data);
|
||||
db.run('UPDATE questions SET explanation = $explanation WHERE id = $id', {
|
||||
$id: id,
|
||||
$explanation: escape(data.explanation)
|
||||
}, function (err) {
|
||||
if (err) {
|
||||
console.log('error updating explanation for', id, ':', err);
|
||||
res.sendStatus(500);
|
||||
} else {
|
||||
res.sendStatus(200);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('invalid explanation for', id, ':', data);
|
||||
res.sendStatus(400);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/qa', cors(corsOptions), function (req, res) {
|
||||
var data = req.body;
|
||||
console.log('NEW QA DATA:', JSON.stringify(data));
|
||||
if (!data.version || !data.data || data.data.length < 15) {
|
||||
console.log('invalid qa data', !data.version, !data.data, data.data.length < 15);
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
db.serialize(function() {
|
||||
var aid = null;
|
||||
db.run("INSERT INTO answering (version, href) VALUES ($version,$href)", {
|
||||
$version: data.version,
|
||||
$href: data.href
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
console.log('inserting answering failed:', err);
|
||||
return;
|
||||
}
|
||||
console.log('inserted:', this.changes, 'lastID:', this.lastID);
|
||||
aid = this.lastID;
|
||||
if (aid) {
|
||||
var qstmt = db.prepare("REPLACE INTO questions (id, text) VALUES (?,?)");
|
||||
var astmt = db.prepare("INSERT INTO answers (aid, qid, text, correct, checked) VALUES (?,?,?,?,?)");
|
||||
for (var i = 0; i < data.data.length; i++) {
|
||||
var q = data.data[i];
|
||||
qstmt.run(q.id, q.question);
|
||||
for (var j = 0; j < q.answers.length; j++) {
|
||||
var a = q.answers[j];
|
||||
astmt.run(aid, q.id, a.text, a.correct, a.checked);
|
||||
}
|
||||
}
|
||||
qstmt.finalize();
|
||||
astmt.finalize();
|
||||
|
||||
res.sendStatus(200);
|
||||
} else {
|
||||
console.log('no answering id');
|
||||
res.sendStatus(400);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/img', cors(corsOptions), function (req, res) {
|
||||
var data = req.body;
|
||||
if (!data.version || !data.data.id || !data.data.src || !data.data.dataUri) {
|
||||
console.log('invalid img data', !data.version, !data.data.id, !data.data.src, !data.data.dataUri);
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
var img = data.data;
|
||||
console.log('NEW IMG DATA:', JSON.stringify(data).substring(0,200));
|
||||
|
||||
var istmt = db.run("REPLACE INTO images (id, src, dataUri) VALUES (?,?,?)", img.id, img.src, img.dataUri, function (err) {
|
||||
if (err) {
|
||||
console.log('saving image ' + img.id + 'failed:', err);
|
||||
res.sendStatus(400);
|
||||
} else {
|
||||
res.sendStatus(200);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
app.listen(3000, function () {
|
||||
console.log('Example app listening on port 3000!')
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "qadigger",
|
||||
"version": "0.0.1",
|
||||
"description": "Tampermonkey script and Node.js Express server to gather questions and answers for later analysis, hobby project",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Raidok",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.15.2",
|
||||
"cors": "^2.8.1",
|
||||
"escape-html": "^1.0.3",
|
||||
"express": "^4.14.0",
|
||||
"pug": "^2.0.0-beta6",
|
||||
"sqlite3": "^3.1.8"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
const CREATE_TABLE_IMAGES = "CREATE TABLE IF NOT EXISTS images (id INTEGER PRIMARY KEY, src TEXT, dataUri TEXT)";
|
||||
const CREATE_TABLE_QUESTIONS = "CREATE TABLE IF NOT EXISTS questions (id INTEGER PRIMARY KEY, updated DATETIME DEFAULT CURRENT_TIMESTAMP, text TEXT)";
|
||||
const CREATE_TABLE_ANSWERS = "CREATE TABLE IF NOT EXISTS answers (id INTEGER PRIMARY KEY, aid INTEGER, qid INTEGER, text TEXT, correct INTEGER, checked INTEGER)";
|
||||
const CREATE_TABLE_ANSWERING = "CREATE TABLE IF NOT EXISTS answering (id INTEGER PRIMARY KEY, created DATETIME DEFAULT CURRENT_TIMESTAMP, version TEXT, href TEXT)";
|
||||
|
||||
const SELECT_ALL = "SELECT q.id AS qid, q.text AS question, i.dataUri AS image, T2.id AS aid, T2.text AS answer, T2.correct FROM questions q \
|
||||
LEFT JOIN (SELECT qid, MAX(aid) AS maxid FROM answers GROUP BY qid) AS T1 ON q.id = T1.qid \
|
||||
LEFT JOIN answers AS T2 ON T2.aid = T1.maxid AND T2.qid = q.id \
|
||||
LEFT JOIN images AS i ON i.id = q.id ORDER BY T2.id DESC";
|
||||
|
||||
|
||||
|
||||
module.exports = {
|
||||
SELECT_ALL: SELECT_ALL
|
||||
}
|
||||
|
||||
|
||||
app.get('/top-mistakes', function (req, res) {
|
||||
db.all("SELECT q.id AS qid, q.text as question, a.acount AS mistakes FROM questions q " +
|
||||
"JOIN (SELECT qid, count(*) as acount FROM answers WHERE correct != checked GROUP BY aid,qid) AS a ON a.qid = q.id ORDER BY mistakes DESC",
|
||||
function (err, rows) {
|
||||
res.render('index', { questions: rows });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,105 @@
|
|||
// ==UserScript==
|
||||
// @name AKparse
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 0.1
|
||||
// @description try to take over the world!
|
||||
// @author Raidok
|
||||
// @match http://localhost:8080/*
|
||||
// @grant none
|
||||
// ==/UserScript==
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
if (document.querySelectorAll('td.popup-contents')[0].childNodes[0].textContent.indexOf('viga') === -1) {
|
||||
console.log('script aborted');
|
||||
return;
|
||||
}
|
||||
|
||||
function getDataUri(url, callback) {
|
||||
var image = new Image();
|
||||
|
||||
image.onload = function () {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
|
||||
canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size
|
||||
|
||||
canvas.getContext('2d').drawImage(this, 0, 0);
|
||||
|
||||
console.log('loaded', url);
|
||||
callback(canvas.toDataURL('image/jpeg'));
|
||||
};
|
||||
|
||||
image.src = url;
|
||||
console.log('loading', url);
|
||||
}
|
||||
|
||||
function nodeListToArray(list) {
|
||||
var array= new Array(list.length);
|
||||
for (var i= 0, n= list.length; i<n; i++)
|
||||
array[i]= list[i];
|
||||
return array;
|
||||
}
|
||||
|
||||
function parse() {
|
||||
return nodeListToArray(document.querySelectorAll('table.box td.box')).map(el => {
|
||||
var tds = el.querySelectorAll('td');
|
||||
if (!tds) {
|
||||
console.log('no tds!');
|
||||
}
|
||||
var q = tds[0].innerText;
|
||||
console.log(q, tds);
|
||||
var img = tds[1].querySelector('img');
|
||||
var imgSrc = img ? img.getAttribute('src') : undefined;
|
||||
var i = imgSrc ? { src: imgSrc } : undefined;
|
||||
var aHtml = tds[2];
|
||||
//console.log('vastused:', tds[2].innerHTML);
|
||||
var id = aHtml.querySelector('input[type=hidden]').getAttribute("value");
|
||||
var as = nodeListToArray(aHtml.querySelectorAll('input[type=checkbox]')).map(x => {
|
||||
var checked = !!x.getAttribute('checked');
|
||||
var green = !!(x.nextElementSibling.getAttribute('style') || '').match(/color:.?green/);
|
||||
var correct = green ? checked : !checked;
|
||||
return { checked: checked, green: green, correct: correct, text: x.nextElementSibling.innerText };
|
||||
});
|
||||
console.log(q,i,as);
|
||||
return { id: id, question: q, img: i, answers: as };
|
||||
});
|
||||
}
|
||||
|
||||
function send(what, data) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open('POST', 'http://localhost:3000/' + what);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200 || xhr.status === 204) {
|
||||
console.log('Success! Response:' + xhr.responseText);
|
||||
} else {
|
||||
console.log('Request failed. Responded ' + xhr.status + ' with content:' + xhr.responseText);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(JSON.stringify({
|
||||
version: GM_info.script.version,
|
||||
href: window.location.href,
|
||||
userAgent: window.agent,
|
||||
data: data
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
var data = parse();
|
||||
send('qa', data);
|
||||
|
||||
var itemsProcessed = 0;
|
||||
|
||||
data.forEach((item, index, array) => {
|
||||
|
||||
if (item.img && item.img.src) {
|
||||
getDataUri(item.img.src, function(dataUri) {
|
||||
send('img', { id: item.id, src: item.img.src, dataUri: dataUri });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
})();
|
|
@ -0,0 +1,80 @@
|
|||
html
|
||||
head
|
||||
title QA
|
||||
style(media='screen', type='text/css').
|
||||
h4 {
|
||||
margin: 40px 0 10px;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
.true:hover {
|
||||
color: green;
|
||||
}
|
||||
.false:hover {
|
||||
color: red;
|
||||
}
|
||||
.explanation {
|
||||
color: #999;
|
||||
}
|
||||
script.
|
||||
function postExplanation(id, explanation) {
|
||||
if (!id) {
|
||||
console.log('no id!');
|
||||
return;
|
||||
}
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open('POST', 'http://localhost:3000/explanation/' + id);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200 || xhr.status === 204) {
|
||||
console.log('Success! Response:' + xhr.responseText);
|
||||
} else {
|
||||
console.log('Request failed. Responded ' + xhr.status + ' with content:' + xhr.responseText);
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(JSON.stringify({explanation: explanation}));
|
||||
}
|
||||
function edit(editLink) {
|
||||
var saveLink = editLink.nextSibling
|
||||
, textArea = editLink.previousSibling
|
||||
, div = textArea.previousSibling
|
||||
, text = div.innerText;
|
||||
console.log('copying', text);
|
||||
textArea.value = text;
|
||||
|
||||
editLink.setAttribute('style', 'display:none');
|
||||
saveLink.removeAttribute('style');
|
||||
textArea.removeAttribute('style');
|
||||
}
|
||||
function save(saveLink) {
|
||||
var id = saveLink.parentNode.getAttribute('data-qid')
|
||||
, editLink = saveLink.previousSibling
|
||||
, textArea = editLink.previousSibling
|
||||
, div = textArea.previousSibling
|
||||
, text = textArea.value;
|
||||
console.log('posting', id, text);
|
||||
div.innerText = text;
|
||||
|
||||
editLink.removeAttribute('style');
|
||||
saveLink.setAttribute('style', 'display:none');
|
||||
textArea.setAttribute('style', 'display:none');
|
||||
postExplanation(id, text);
|
||||
}
|
||||
body
|
||||
ul(style='list-style-type:none')
|
||||
each q in questions
|
||||
li(data-qid=q.id)
|
||||
h4= q.question
|
||||
p.explanation !{ q.explanation ? q.explanation.replace(/\n/g, "<br>") : "" }
|
||||
textarea(style='display:none')
|
||||
a(href='javascript:void(0)',onclick='edit(this)') edit
|
||||
a(href='javascript:void(0)',onclick='save(this)',style='display:none') save
|
||||
p
|
||||
img(src=q.image)
|
||||
ul(style='list-style-type:decimal')
|
||||
each a in q.answers
|
||||
li(class=a.correct?'true':'false')= a.answer
|
Loading…
Reference in New Issue