Skip to content

Instantly share code, notes, and snippets.

@walidbagh
Last active August 7, 2023 19:20
Show Gist options
  • Save walidbagh/c310a2d209d06a80763dfd8b3e8862fa to your computer and use it in GitHub Desktop.
Save walidbagh/c310a2d209d06a80763dfd8b3e8862fa to your computer and use it in GitHub Desktop.
// ==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(/&nbsp;/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(/&nbsp;/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