Last active
August 7, 2023 19:20
-
-
Save walidbagh/c310a2d209d06a80763dfd8b3e8862fa to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Qcm Grabber | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1 | |
// @description try to take over the world! | |
// @author You | |
// @require https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js | |
// @match https://app.siamois.co/quiz/* | |
// @include https://web.siamois.co | |
// @icon https://www.google.com/s2/favicons?sz=64&domain=siamois.co | |
// // @unwrap | |
// @grant GM_cookie | |
// @grant GM.cookie | |
// @run-at document-start | |
// ==/UserScript== | |
function decrypt(encrypted) { | |
let key = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse("gQPEs7txV4m78WfwGFAYRbdS452rCmqE")); | |
encrypted = atob(encrypted); | |
encrypted = JSON.parse(encrypted); | |
// console.log("Laravel encryption result", encrypted); | |
// IV is base64 encoded in Laravel, expected as word array in cryptojs | |
const iv = CryptoJS.enc.Base64.parse(encrypted.iv); | |
// Value (chipher text) is also base64 encoded in Laravel, same in cryptojs | |
const value = encrypted.value; | |
// Key is base64 encoded in Laravel, word array expected in cryptojs | |
key = CryptoJS.enc.Base64.parse(key); | |
// Decrypt the value, providing the IV. | |
const decrypted = CryptoJS.AES.decrypt(value, key, { iv }); | |
// CryptoJS returns a word array which can be | |
// converted to string like this | |
const data = JSON.parse(decrypted.toString(CryptoJS.enc.Utf8)).data; | |
// console.dir(data); | |
return data; | |
} | |
(function hijackToken() { | |
let oldXHROpen = window.XMLHttpRequest.prototype.open; | |
window.XMLHttpRequest.prototype.open = function(method, url, async, user, password) { | |
// do something with the method, url and etc. | |
if (url === 'https://web.siamois.co/api/users/v1/login/refresh') { | |
this.addEventListener('load', function() { | |
// do something with the response text | |
const response = JSON.parse(this.responseText); | |
// console.info('Intercepted response: ', response); | |
const token = response.data.access_token; | |
// console.log('Token: ', token); | |
window.addEventListener("load", main(token), false); | |
}); | |
} | |
return oldXHROpen.apply(this, arguments); | |
} | |
})(); | |
async function getQuiz(token, quiz=null, url=null) { | |
console.log(quiz, url); | |
const apiHost = 'https://web.siamois.co'; | |
/*let authResponse = await fetch(`${apiHost}/api/users/v1/login/refresh`, { | |
headers: { | |
Accept: 'application/json', | |
}, | |
credentials: 'include' | |
}); | |
authResponse = await authResponse.json(); | |
const refreshToken = authResponse.data.access_token;*/ | |
/*const cookies = await GM.cookie.list({ name: 'refreshToken', url: 'https://web.siamois.co' }); | |
const refreshToken = cookies[0].value;*/ | |
const quizUUID = window.location.pathname.split("/").pop(); | |
url = url ? url + '&rc=token' : `${apiHost}/api/quizzes/v1/get-details/${quizUUID}?page=1&rc=token`; | |
const response = await fetch(url, { | |
headers: { | |
Accept: 'application/json', | |
Authorization: `Bearer ${token}`, | |
"X-Forwarded-For": "127.0.0.1", | |
}, | |
credentials: 'include' | |
}); | |
const data = decrypt(await response.text()); | |
quiz ? quiz.questions.push(...data.questions) : quiz = data; | |
return data.next ? await getQuiz(token, quiz, data.next) : quiz; | |
} | |
function renderQuestion(item, index, p_index = null) { | |
let question = item.question.replace(/<\/?p>/g, "").replace(/ /g, ""); | |
question = `<strong>${p_index ? p_index + 1 + '.' : ''}${index + 1}) ${question}</strong>`; | |
const type = item.type === 'CCQCM' ? 'Cas Clinique' : item.type; | |
const options = item.options.map((o, i) => { | |
const index = item.hint ? i + 1 : (i + 10).toString(36).toUpperCase(); | |
return `<li>${index} - ${o.option}</li>`; | |
}); | |
const answers = item.options | |
.reduce(function (acc, curr, i) { | |
if (curr.is_correct) { | |
acc.push(item.hint ? i + 1 : (i + 10).toString(36).toUpperCase()); | |
} | |
return acc; | |
}, []) | |
.join(" "); | |
const explanation = item.explanation | |
? item.explanation.replace(/ /g, "").replace(/<p>\s*<\/p>/gi, "") | |
: ""; | |
const explanationImages = item.explanation_files | |
? item.explanation_files.map((img, i, arr) => { | |
return `<figure> | |
<img src="${img.image}" alt="${img.description}" /> | |
<figcaption>${i+1}/${arr.length} ${img.description}</figcaption> | |
</figure>`; | |
}) | |
: ''; | |
return `<div class="question"> | |
${question} | |
<small class="type">${type}</small> | |
</div> | |
<ul class="options"> | |
${options.join("")} | |
</ul> | |
<div class="hint">${item.hint||''}</div> | |
<div class="answers">${answers}</div> | |
<div class="explanation">${explanation || ""} ${explanationImages}</div>`; | |
}; | |
async function main(token) { | |
const quiz = await getQuiz(token); | |
console.dir(quiz); | |
const questions = quiz.questions.map((qst, index) => { | |
const tags = qst.tags.map((t, i) => `<small class="tag">${t.name} ${t.year || ""}</small>`); | |
const question = renderQuestion(qst, index); | |
let subQuestions = null; | |
if (qst.type === 'CCQCM' || qst.sub_questions) { | |
subQuestions = qst.sub_questions | |
.map((sub_qst, sub_i) => renderQuestion(sub_qst, sub_i, index)) | |
.join(''); | |
subQuestions = `<div class="sub-questions">${subQuestions}</div>`; | |
} | |
return `<div class="card"> | |
<div class="card-content"> | |
<div class="content"> | |
<div class="meta"> | |
<small class="topic">${qst.topic} -</small> | |
${tags.join()} | |
</div> | |
${question} | |
${subQuestions||''} | |
</div> | |
</div> | |
</div>`; | |
}); | |
const bodyHtml = ` | |
<a id="toggleExp" href="#">Explications</a> | |
<a id="toggleInf" href="#">Info</a> | |
<div class="container A4"> | |
<h3 class="quiz-name">${quiz.quiz_name}</h3> | |
${questions.join("")} | |
</div>`; | |
const cssStyle = document.createElement("style"); | |
cssStyle.innerHTML = `@media print{#toggleExp,#toggleInf{display:none}}.A4{width:210mm;height:296mm}.quiz-name{text-align:center} | |
.content{font-size:18px;line-height:1.8em;page-break-inside:avoid;}.meta{color:gray;}.type{color:red;margin-left:8px} | |
.sub-questions{position:relative;margin-left:40px} | |
.sub-questions::before{content:"";display:block;width:0;position:absolute;top:0;bottom:0;left:-28px;border-left: 1px solid} | |
.options{list-style-type:none;margin-bottom:0;margin-top:0;}.hint{text-align:center}.answers{text-align:right;font-weight:bolder} | |
.explanation{padding:1rem;border:1px solid #000;margin-bottom:1em;}.explanation:empty{display:none;} | |
.explanation figure{width:unset!important;border:thin #c0c0c0 solid;padding:5px;}figcaption{text-align:center;}.explanation img{max-width:100%;height:auto}.hide{display:none}`; | |
const jsScript = document.createElement("script"); | |
jsScript.innerHTML = ` | |
document.getElementById("toggleExp").addEventListener("click", (e) => { | |
e.preventDefault(); | |
document.querySelectorAll(".explanation").forEach((exp) => exp.classList.toggle("hide")); | |
}); | |
document.getElementById("toggleInf").addEventListener("click", (e) => { | |
e.preventDefault(); | |
document.querySelectorAll(".meta").forEach((exp) => exp.classList.toggle("hide")); | |
}); | |
`; | |
const htmlDoc = document.implementation.createHTMLDocument(); | |
htmlDoc.body.innerHTML = bodyHtml; | |
htmlDoc.body.prepend(cssStyle); | |
htmlDoc.body.appendChild(jsScript); | |
const win = window.open("about:blank", "_blank"); | |
win.document.write(htmlDoc.documentElement.innerHTML); | |
win.document.close(); | |
win.focus(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment