Last active
December 8, 2023 21:25
-
-
Save griffi-gh/2cf11b9763b5158337d649d3b64ae154 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 Naurok Bypass v2 | |
// @author griffi-gh | |
// @namespace griffi-gh | |
// @description Fetches answers to *all* Naurok quizes | |
// @version 8.1 | |
// @license MIT | |
// @match *://naurok.com.ua/test/*.html | |
// @grant GM_setValue | |
// @grant GM_getValue | |
// @grant GM_getResourceURL | |
// @run-at document-start | |
// @inject-into content | |
// @sandbox DOM | |
// @connect naurok.com.ua | |
// @icon https://play-lh.googleusercontent.com/scIkpmsUJTfDbV39X0rb-AvxbgxOrpa9zIGJQqDHP1VbuBTmortXomSSWVZnpErwyA=w480-h960 | |
// @homepageURL https://greasyfork.org/uk/scripts/461662-naurok-bypass-v2 | |
// @resource LOADING_IMAGE https://media.tenor.com/MlCeUwzn2nEAAAAM/troll-lol.gif | |
// @resource TEST_IMAGE_URL https://cdn-icons-png.flaticon.com/512/190/190411.png | |
// @resource TEST_UPDATE_IMAGE_URL https://cdn-icons-png.flaticon.com/512/1601/1601884.png | |
// @resource ERROR_IMAGE_URL https://media.tenor.com/hA1b1zIqnHkAAAAd/among-us.gif | |
// ==/UserScript== | |
"use strict"; { | |
console.log("Naurok Bypass v2"); | |
// Check if already loaded | |
if (window._BNAUROK2 != null) { | |
console.error("already loaded"); | |
return; | |
} else { | |
window._BNAUROK2 = true; | |
} | |
//Required to work around the false session issue | |
//Used only if a direct request fails | |
const SESSION_PROXY = "https://eobzz8g6oxzrky0.m.pipedream.net/"; | |
let ERROR_IMAGE_URL, LOADING_IMAGE, TEST_IMAGE_URL, TEST_UPDATE_IMAGE_URL; | |
if (window.GM_getResourceURL) { | |
//Use GM resources if possible | |
console.log("Using GM resources"); | |
LOADING_IMAGE = GM_getResourceURL("LOADING_IMAGE", true); | |
TEST_IMAGE_URL = GM_getResourceURL("TEST_IMAGE_URL", true); | |
TEST_UPDATE_IMAGE_URL = GM_getResourceURL("TEST_UPDATE_IMAGE_URL", true); | |
ERROR_IMAGE_URL = GM_getResourceURL("ERROR_IMAGE_URL", true); | |
} else { | |
console.log("Using URL resources"); | |
LOADING_IMAGE = "https://media.tenor.com/MlCeUwzn2nEAAAAM/troll-lol.gif"; | |
TEST_IMAGE_URL = "https://cdn-icons-png.flaticon.com/512/190/190411.png"; | |
TEST_UPDATE_IMAGE_URL = "https://cdn-icons-png.flaticon.com/512/1601/1601884.png"; | |
ERROR_IMAGE_URL = "https://media.tenor.com/hA1b1zIqnHkAAAAd/among-us.gif"; | |
} | |
const ls_key = `cached-${document.location.pathname.replaceAll("/", "-").slice(1, -5).toLowerCase()}`; | |
async function loadStuff() { | |
//Clean up | |
pre_display() | |
//Get the hostname | |
const hostname = window.location.hostname; | |
console.log("Host:", hostname); | |
//Get test path | |
const base = document.location.href.slice(0, -5); | |
console.log("Base URL:", base); | |
//Load the homework creation page | |
const set_text = await fetch(base + "/set").then(x => x.text()); | |
const set_document = document.createElement("html"); | |
set_document.innerHTML = set_text; | |
console.log("Set document:", set_document); | |
//Get form data and modify it | |
const set_form = set_document.querySelector("#w0"); | |
const set_form_data = new FormData(set_form); | |
set_form_data.set("Homework[deadline_day]", "9999-01-01"); | |
set_form_data.set("Homework[show_answer]", "1"); | |
console.log("Set form data:", set_form_data); | |
//Start homework | |
const homework_res = await fetch(set_form.action, { | |
redirect: 'follow', | |
method: 'POST', | |
credentials: 'include', | |
body: set_form_data, | |
}); | |
//Get homework url and id | |
const homework_url = homework_res.url; | |
const homework_id = homework_url.split("/").at(-1); | |
console.log("Homework url:", homework_url); | |
//Get homework document object | |
const homework_text = await homework_res.text(); | |
const homework_document = document.createElement("html"); | |
homework_document.innerHTML = homework_text; | |
console.log("Homework document:", homework_document); | |
//Get the CSRF token and create FormData from it | |
const homework_csrf_param = homework_document.querySelector('meta[name="csrf-param"]').content; | |
const homework_csrf_token = homework_document.querySelector('meta[name="csrf-token"]').content; | |
const homework_csrf_form_data = new FormData(); | |
homework_csrf_form_data.set(homework_csrf_param, homework_csrf_token); | |
console.log("CSRF param/token:", homework_csrf_param, homework_csrf_token); | |
//Get join code | |
const homework_code = homework_document.querySelector(".homework-code").textContent; | |
console.log("Homework join code:", homework_code); | |
//Load the join page | |
const join_text = await fetch(`https://${hostname}/test/join?gamecode=${homework_code}`).then(res => res.text()); | |
const join_document = document.createElement('html'); | |
join_document.innerHTML = join_text; | |
//Get form data and fill in the username | |
const join_form = join_document.querySelector("#participate-form-code"); | |
const join_form_data = new FormData(join_form); | |
const username = "[object Object]"; | |
join_form_data.set("JoinForm[name]", username); //troll naurok developers while we're at it lol | |
//Start homework session | |
const join_res = await fetch(join_form.action, { | |
redirect: 'follow', | |
method: 'POST', | |
credentials: 'include', | |
body: new URLSearchParams(join_form_data).toString(), | |
headers: { | |
"Content-Type": "application/x-www-form-urlencoded", | |
}, | |
}); | |
console.log("Joined with username", username) | |
//Get test session address | |
const test_session_url = join_res.url; | |
console.log("Test session URL:", test_session_url); | |
//Get the test session document | |
const session_text = await fetch(test_session_url).then(res => res.text()); | |
const session_document = document.createElement("html"); | |
session_document.innerHTML = session_text; | |
//Get session id | |
const testik_elem = session_document.querySelector('[ng-app="testik"]'); | |
const ng_init = testik_elem.getAttribute("ng-init"); | |
const ng_init_numbers = ng_init.match(/[0-9]+/g); | |
const session_id = ng_init_numbers[1] || 0; | |
console.log("Session id", session_id); | |
//Get session info | |
let session_info = await fetch(`https://${hostname}/api2/test/sessions/${session_id}`, { | |
credentials: "include", | |
headers: { | |
'Accept': 'application/json, text/plain, */*', | |
'Content-Type': 'application/json' | |
}, | |
redirect: 'follow', | |
}).then(x => x.json()); | |
console.log("Session info: ", session_info); | |
//Work around weird issue | |
if (session_info == false) { | |
console.log("Oops, need to proxy the request"); | |
session_info = await fetch(SESSION_PROXY, { | |
method: "POST", | |
headers: { | |
'Accept': 'application/json, text/plain, */*', | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
session: session_id | |
}) | |
}).then(x => x.json()); | |
console.log("Session info (proxied):", session_info); | |
} | |
//Find the latest question | |
const {latest_question, questions} = session_info; | |
const question = latest_question ? questions.find(question => question.id == latest_question) : questions[0]; | |
console.log("Question:", question); | |
//Get the answer id | |
const answer_id = question.options[0].id.toString(); | |
console.log("Answer ID:", answer_id); | |
//Answer the question | |
await fetch(`https://${hostname}/api2/test/responses/answer`, { | |
method: "PUT", | |
credentials: "include", | |
headers: { | |
'Accept': 'application/json, text/plain, */*', | |
'Content-Type': 'application/json' | |
}, | |
body: JSON.stringify({ | |
"session_id": session_id, | |
"answer": [answer_id], | |
"question_id": question.id, | |
"show_answer": 1, | |
"type": "quiz", | |
"point": question.point.toString(), | |
"homeworkType": question.type, | |
"homework": true | |
}), | |
}); | |
console.log("Responded to the question"); | |
//End the session | |
const end_sess_data = await fetch(`https://${hostname}/api2/test/sessions/end/${session_id}`, { | |
"method": "PUT", | |
"credentials": "include", | |
}).then(res => res.json()); | |
const end_sess_uuid = end_sess_data.session.uuid; | |
console.log("Session UUID:", end_sess_uuid); | |
//Fetch the end page | |
const test_end_text = await fetch(`https://${hostname}/test/complete/${end_sess_uuid}`, { | |
redirect: 'follow' | |
}).then(res => res.text()); | |
//Get the text end document | |
const test_end_document = document.createElement("html"); | |
test_end_document.innerHTML = test_end_text; | |
//Get the answers | |
const answers = test_end_document.querySelector(".homework-stats"); | |
console.log("Answer element:", answers); | |
//Display answers | |
display_answers(answers); | |
console.log("Answers displayed"); | |
//Stop homework | |
await fetch(`https://${hostname}/test/homework/${homework_id}/stop`, { | |
method: 'POST', | |
credentials: 'include', | |
body: homework_csrf_form_data, | |
}); | |
console.log("Homework stopped"); | |
//Delete homework | |
await fetch(`https://${hostname}/test/homework/${homework_id}/delete`, { | |
method: 'POST', | |
credentials: 'include', | |
body: homework_csrf_form_data, | |
}); | |
console.log("Homework deleted"); | |
console.log("DONE"); | |
return answers | |
}; | |
function pre_display() { | |
//Delete previously displayed | |
Array.from(document.querySelectorAll(".question-view-item")).forEach(item => item.remove()); | |
//Clear the regular questions | |
Array.from(document.querySelectorAll(".answer-sheet")).forEach(item => item.remove()); | |
} | |
function display_answers(answers) { | |
pre_display() | |
//Add classes | |
answers.classList.add("row"); | |
answers.classList.add("answer-sheet") | |
//HACK: Remove "- your answer" text | |
answers.innerHTML = answers.innerHTML.replaceAll("<em>— ваша відповідь</em>", ""); | |
//Add answers to the page | |
const afer_element = document.querySelector(".block-head"); | |
afer_element.parentNode.insertBefore(answers, afer_element.nextSibling); | |
}; | |
async function loadStuffWriteCache() { | |
const answers = await loadStuff(); | |
const elem = document.createElement("div"); | |
display_answers(elem); | |
elem.appendChild(answers.cloneNode(true)); | |
(window.GM_setValue ?? localStorage.setItem.bind(localStorage))(ls_key, elem.innerHTML); | |
} | |
//Preload images | |
const images = [] | |
{ | |
window._cow_taxes = images; //keep the reference alive | |
images["loading"] = new Image(); | |
images["loading"].src = LOADING_IMAGE; | |
images["test"] = new Image(); | |
images["test"].src = TEST_IMAGE_URL; | |
images["update"] = new Image(); | |
images["update"].src = TEST_UPDATE_IMAGE_URL; | |
images["error"] = new Image(); | |
images["error"].src = ERROR_IMAGE_URL; | |
} | |
function loadErrorHandler(err) { | |
console.error(err); | |
const btn = document.querySelector(".clicky-click"); | |
btn.querySelector("img").replaceWith(images.error); | |
const label = btn.querySelector("span") | |
label.textContent = "Помилка\n"+err.toString()+"\n"; | |
label.innerHTML = label.innerHTML.replaceAll("\n", "<br>"); | |
label.innerHTML += ` | |
FIX 1 - Натисни кнопку ще раз<br> | |
FIX 2 - <a href='https://naurok.com.ua/test/join?gamecode=0' target="_blank">Відкрий і закрий цю сторінку</a><br> | |
FIX 3 - <a href='https://naurok.com.ua/login' target="_blank">Увійди в аккаунт Наурок</a><br> | |
FIX 4 - Спробуй ще раз через 5 хвилин | |
`; | |
} | |
async function loadStuffAndWriteCacheWithErrorHandler() { | |
try { | |
await loadStuffWriteCache(); | |
return true; | |
} catch(err) { | |
loadErrorHandler(err); | |
return false; | |
} | |
} | |
//Load cached answers | |
let is_cached = false; | |
let cached_element = null; | |
if ((window.GM_getValue ?? localStorage.getItem.bind(localStorage))(ls_key, null)) { | |
console.log("Cached found:", ls_key); | |
const elem = document.createElement("div"); | |
try { | |
elem.innerHTML = (GM_getValue ?? localStorage.getItem.bind(localStorage))(ls_key, null); | |
cached_element = elem.firstChild; | |
is_cached = true; | |
(cached_element.querySelector(".chicken-beef")?.classList.add("answer-sheet")) && console.warn("Cache contains chicken beef"); | |
} catch (e) { | |
console.error("Cache invalid:", e); | |
is_cached = false; | |
cached_element = null; | |
} | |
} | |
const MAIN = async () => { | |
//Display cached stuff | |
if (is_cached) { | |
try { | |
display_answers(cached_element); | |
console.log("Cached answer displayed!") | |
} catch(e) { | |
console.error("Cache invalid:", e); | |
is_cached = false; | |
cached_element = null; | |
} | |
} | |
//Add CSS | |
{ | |
const style = ` | |
.answer-sheet { | |
padding: 1.33rem; | |
} | |
/* style indicator colors*/ | |
.answer-sheet .homework-stat-option-value.incorect :is(.quiz,.multiquiz) { | |
background: #cccccc !important; | |
color: black !important; | |
} | |
.answer-sheet .homework-stat-option-value.correct :is(.quiz,.multiquiz) { | |
background: #23c552 !important; | |
color: black !important; | |
} | |
/* fix indicator offset */ | |
.answer-sheet .homework-stat-option-value :is(.quiz,.multiquiz) { | |
top: .5rem !important; | |
} | |
/* Green bg on correct*/ | |
.answer-sheet .homework-stat-option-value.correct { | |
background: linear-gradient(to bottom, rgba(0,0,0,0), #e3f7e9 15%, #e3f7e9 85%, rgba(0,0,0,0)) !important; | |
border-radius: .25rem !important; | |
} | |
/* fix uneven p padding*/ | |
.answer-sheet .homework-stat-option-value p { | |
margin: 0.5rem 0.25rem !important; | |
} | |
/* hide q ranking */ | |
.answer-sheet .question-label { | |
display: none !important; | |
} | |
/* hide weird orange overlays */ | |
.answer-sheet .ql-cursor { | |
display: none !important; | |
} | |
/* hide correctness indicators*/ | |
.answer-sheet .content-block.success, | |
.answer-sheet .content-block.skipped, | |
.answer-sheet .content-block.failed, | |
.answer-sheet .content-block.partial { | |
border-left: none !important; | |
} | |
/* style the main button */ | |
.clicky-click { | |
display: flex; | |
width: 100%; | |
border-width: 0; | |
font-family: inherit; | |
font-size: inherit; | |
font-style: inherit; | |
font-weight: inherit; | |
line-height: inherit; | |
margin-bottom: 0 !important; | |
} | |
#cb_wrapper { | |
display: blck; | |
text-align: center; | |
} | |
#auto_load_cb { | |
margin-right: .25rem; | |
} | |
#auto_load_cb ~ label { | |
font-weight: unset; | |
} | |
/* Use flex to style our button */ | |
.test-action-button.clicky-click { | |
display: flex !important; | |
flex-direction: column !important; | |
height: unset !important; | |
gap: 10px !important; | |
} | |
.test-action-button.clicky-click * { | |
position: unset !important; | |
} | |
/* This applies to ALL buttons (makes them a bit fancier because why not) */ | |
.test-action-button { | |
transition: all .25s !important; | |
border: 1px solid rgba(0,0,0,.1) !important; | |
border-radius: 10px !important; | |
} | |
.test-action-button:hover { | |
background: #f0f0f0 !important; | |
} | |
.test-action-button:hover > * { | |
filter: drop-shadow(0px 0px 4px #dddddd); | |
} | |
.test-action-button > img { | |
transition: transform .25s !important; | |
} | |
.test-action-button:not(:disabled):hover > img { | |
transform: scale(0.9) rotate(-3deg); | |
} | |
/* todo: style auto load checkbox */ | |
#auto_load_cb {} | |
/* fix scrolling */ | |
body { | |
overflow: auto !important; | |
} | |
/* Remove ads */ | |
/* TODO: fix! hides answers too */ | |
/*.col-sm-8.col-md-9 { | |
display: none !important; | |
}*/ | |
/* Fix em coloring */ | |
.homework-stat-option-value em { | |
color: inherit !important; | |
} | |
`; | |
const style_elem = document.createElement("style"); | |
style_elem.textContent = style; | |
document.head.appendChild(style_elem); | |
} | |
//Create answers button | |
{ | |
const button = document.createElement("button"); | |
button.type = "button"; | |
button.classList.add("test-action-button"); | |
button.classList.add("clicky-click"); | |
button.appendChild(images.test); | |
const text_elem = document.createElement("span"); | |
text_elem.textContent = "Завантажити відповіді"; | |
button.appendChild(text_elem); | |
if (is_cached) { | |
button.querySelector('img').replaceWith(images.update); | |
text_elem.textContent = "Оновити відповіді"; | |
} | |
button.addEventListener("click", async () => { | |
button.querySelector('img').replaceWith(images.loading); | |
text_elem.textContent = "Завантаження..."; | |
button.disabled = true; | |
if (await loadStuffAndWriteCacheWithErrorHandler()) { | |
button.querySelector('img').replaceWith(images.update); | |
text_elem.textContent = "Оновити відповіді"; | |
} | |
button.disabled = false; | |
}); | |
const buttons = document.querySelector(".single-test-actions"); | |
buttons.prepend(button); | |
} | |
//Create auto load toggle | |
{ | |
//Create checkbox | |
const auto_load_cb = document.createElement("input"); | |
auto_load_cb.id = "auto_load_cb"; | |
auto_load_cb.type = "checkbox"; | |
const save_state = () => { | |
(window.GM_setValue || localStorage.setItem.bind(localStorage))("auto-load", auto_load_cb.checked ? "1" : "0"); | |
} | |
auto_load_cb.checked = ((window.GM_getValue || localStorage.getItem.bind(localStorage))("auto-load") || "0") == "1"; | |
save_state(); | |
auto_load_cb.addEventListener("change", save_state); | |
if (!is_cached && auto_load_cb.checked) { | |
document.querySelector(".clicky-click").click(); | |
} | |
//Add it | |
const cb_wrapper = document.createElement("div"); | |
cb_wrapper.id = "cb_wrapper"; | |
const cb_label = document.createElement("label"); | |
cb_label.textContent = "Автоматично завантажувати відповіді"; | |
cb_label.setAttribute("for", auto_load_cb.id); | |
cb_wrapper.appendChild(auto_load_cb); | |
cb_wrapper.appendChild(cb_label); | |
const afer_element = document.querySelector(".clicky-click"); | |
afer_element.parentNode.insertBefore(cb_wrapper, afer_element.nextSibling); | |
} | |
}; | |
// Run if loaded late | |
if ((document?.readyState == "interactive") || (document?.readyState == "complete")) { | |
MAIN(); | |
} else { | |
document.addEventListener("DOMContentLoaded", MAIN); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment