Naurok Bypass v2

Fetches answers to *all* Naurok quizes

安装此脚本?
作者推荐脚本

您可能也喜欢Naurok Bypass v2: Extras

安装此脚本
  1. // ==UserScript==
  2. // @name Naurok Bypass v2
  3. // @author griffi-gh
  4. // @namespace griffi-gh
  5. // @description Fetches answers to *all* Naurok quizes
  6. // @version 8.1
  7. // @license MIT
  8. // @match *://naurok.com.ua/test/*.html
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_getResourceURL
  12. // @run-at document-start
  13. // @inject-into content
  14. // @sandbox DOM
  15. // @connect naurok.com.ua
  16. // @icon https://play-lh.googleusercontent.com/scIkpmsUJTfDbV39X0rb-AvxbgxOrpa9zIGJQqDHP1VbuBTmortXomSSWVZnpErwyA=w480-h960
  17. // @homepageURL https://greasyfork.org/uk/scripts/461662-naurok-bypass-v2
  18. // @resource LOADING_IMAGE https://media.tenor.com/MlCeUwzn2nEAAAAM/troll-lol.gif
  19. // @resource TEST_IMAGE_URL https://cdn-icons-png.flaticon.com/512/190/190411.png
  20. // @resource TEST_UPDATE_IMAGE_URL https://cdn-icons-png.flaticon.com/512/1601/1601884.png
  21. // @resource ERROR_IMAGE_URL https://media.tenor.com/hA1b1zIqnHkAAAAd/among-us.gif
  22. // ==/UserScript==
  23.  
  24. "use strict"; {
  25.  
  26. console.log("Naurok Bypass v2");
  27.  
  28. // Check if already loaded
  29. if (window._BNAUROK2 != null) {
  30. console.error("already loaded");
  31. return;
  32. } else {
  33. window._BNAUROK2 = true;
  34. }
  35.  
  36. //Required to work around the false session issue
  37. //Used only if a direct request fails
  38. const SESSION_PROXY = "https://eobzz8g6oxzrky0.m.pipedream.net/";
  39.  
  40. let ERROR_IMAGE_URL, LOADING_IMAGE, TEST_IMAGE_URL, TEST_UPDATE_IMAGE_URL;
  41. if (window.GM_getResourceURL) {
  42. //Use GM resources if possible
  43. console.log("Using GM resources");
  44. LOADING_IMAGE = GM_getResourceURL("LOADING_IMAGE", true);
  45. TEST_IMAGE_URL = GM_getResourceURL("TEST_IMAGE_URL", true);
  46. TEST_UPDATE_IMAGE_URL = GM_getResourceURL("TEST_UPDATE_IMAGE_URL", true);
  47. ERROR_IMAGE_URL = GM_getResourceURL("ERROR_IMAGE_URL", true);
  48. } else {
  49. console.log("Using URL resources");
  50. LOADING_IMAGE = "https://media.tenor.com/MlCeUwzn2nEAAAAM/troll-lol.gif";
  51. TEST_IMAGE_URL = "https://cdn-icons-png.flaticon.com/512/190/190411.png";
  52. TEST_UPDATE_IMAGE_URL = "https://cdn-icons-png.flaticon.com/512/1601/1601884.png";
  53. ERROR_IMAGE_URL = "https://media.tenor.com/hA1b1zIqnHkAAAAd/among-us.gif";
  54. }
  55.  
  56. const ls_key = `cached-${document.location.pathname.replaceAll("/", "-").slice(1, -5).toLowerCase()}`;
  57.  
  58. async function loadStuff() {
  59. //Clean up
  60. pre_display()
  61.  
  62. //Get the hostname
  63. const hostname = window.location.hostname;
  64. console.log("Host:", hostname);
  65.  
  66. //Get test path
  67. const base = document.location.href.slice(0, -5);
  68. console.log("Base URL:", base);
  69.  
  70. //Load the homework creation page
  71. const set_text = await fetch(base + "/set").then(x => x.text());
  72. const set_document = document.createElement("html");
  73. set_document.innerHTML = set_text;
  74. console.log("Set document:", set_document);
  75.  
  76. //Get form data and modify it
  77. const set_form = set_document.querySelector("#w0");
  78. const set_form_data = new FormData(set_form);
  79. set_form_data.set("Homework[deadline_day]", "9999-01-01");
  80. set_form_data.set("Homework[show_answer]", "1");
  81. console.log("Set form data:", set_form_data);
  82.  
  83. //Start homework
  84. const homework_res = await fetch(set_form.action, {
  85. redirect: 'follow',
  86. method: 'POST',
  87. credentials: 'include',
  88. body: set_form_data,
  89. });
  90.  
  91. //Get homework url and id
  92. const homework_url = homework_res.url;
  93. const homework_id = homework_url.split("/").at(-1);
  94. console.log("Homework url:", homework_url);
  95.  
  96. //Get homework document object
  97. const homework_text = await homework_res.text();
  98. const homework_document = document.createElement("html");
  99. homework_document.innerHTML = homework_text;
  100. console.log("Homework document:", homework_document);
  101.  
  102. //Get the CSRF token and create FormData from it
  103. const homework_csrf_param = homework_document.querySelector('meta[name="csrf-param"]').content;
  104. const homework_csrf_token = homework_document.querySelector('meta[name="csrf-token"]').content;
  105. const homework_csrf_form_data = new FormData();
  106. homework_csrf_form_data.set(homework_csrf_param, homework_csrf_token);
  107. console.log("CSRF param/token:", homework_csrf_param, homework_csrf_token);
  108.  
  109. //Get join code
  110. const homework_code = homework_document.querySelector(".homework-code").textContent;
  111. console.log("Homework join code:", homework_code);
  112.  
  113. //Load the join page
  114. const join_text = await fetch(`https://${hostname}/test/join?gamecode=${homework_code}`).then(res => res.text());
  115. const join_document = document.createElement('html');
  116. join_document.innerHTML = join_text;
  117.  
  118. //Get form data and fill in the username
  119. const join_form = join_document.querySelector("#participate-form-code");
  120. const join_form_data = new FormData(join_form);
  121. const username = "[object Object]";
  122. join_form_data.set("JoinForm[name]", username); //troll naurok developers while we're at it lol
  123.  
  124. //Start homework session
  125. const join_res = await fetch(join_form.action, {
  126. redirect: 'follow',
  127. method: 'POST',
  128. credentials: 'include',
  129. body: new URLSearchParams(join_form_data).toString(),
  130. headers: {
  131. "Content-Type": "application/x-www-form-urlencoded",
  132. },
  133. });
  134. console.log("Joined with username", username)
  135.  
  136. //Get test session address
  137. const test_session_url = join_res.url;
  138. console.log("Test session URL:", test_session_url);
  139.  
  140. //Get the test session document
  141. const session_text = await fetch(test_session_url).then(res => res.text());
  142. const session_document = document.createElement("html");
  143. session_document.innerHTML = session_text;
  144.  
  145. //Get session id
  146. const testik_elem = session_document.querySelector('[ng-app="testik"]');
  147. const ng_init = testik_elem.getAttribute("ng-init");
  148. const ng_init_numbers = ng_init.match(/[0-9]+/g);
  149. const session_id = ng_init_numbers[1] || 0;
  150. console.log("Session id", session_id);
  151.  
  152. //Get session info
  153. let session_info = await fetch(`https://${hostname}/api2/test/sessions/${session_id}`, {
  154. credentials: "include",
  155. headers: {
  156. 'Accept': 'application/json, text/plain, */*',
  157. 'Content-Type': 'application/json'
  158. },
  159. redirect: 'follow',
  160. }).then(x => x.json());
  161. console.log("Session info: ", session_info);
  162.  
  163. //Work around weird issue
  164. if (session_info == false) {
  165. console.log("Oops, need to proxy the request");
  166. session_info = await fetch(SESSION_PROXY, {
  167. method: "POST",
  168. headers: {
  169. 'Accept': 'application/json, text/plain, */*',
  170. 'Content-Type': 'application/json'
  171. },
  172. body: JSON.stringify({
  173. session: session_id
  174. })
  175. }).then(x => x.json());
  176. console.log("Session info (proxied):", session_info);
  177. }
  178.  
  179. //Find the latest question
  180. const {latest_question, questions} = session_info;
  181. const question = latest_question ? questions.find(question => question.id == latest_question) : questions[0];
  182. console.log("Question:", question);
  183.  
  184. //Get the answer id
  185. const answer_id = question.options[0].id.toString();
  186. console.log("Answer ID:", answer_id);
  187.  
  188. //Answer the question
  189. await fetch(`https://${hostname}/api2/test/responses/answer`, {
  190. method: "PUT",
  191. credentials: "include",
  192. headers: {
  193. 'Accept': 'application/json, text/plain, */*',
  194. 'Content-Type': 'application/json'
  195. },
  196. body: JSON.stringify({
  197. "session_id": session_id,
  198. "answer": [answer_id],
  199. "question_id": question.id,
  200. "show_answer": 1,
  201. "type": "quiz",
  202. "point": question.point.toString(),
  203. "homeworkType": question.type,
  204. "homework": true
  205. }),
  206. });
  207. console.log("Responded to the question");
  208.  
  209. //End the session
  210. const end_sess_data = await fetch(`https://${hostname}/api2/test/sessions/end/${session_id}`, {
  211. "method": "PUT",
  212. "credentials": "include",
  213. }).then(res => res.json());
  214. const end_sess_uuid = end_sess_data.session.uuid;
  215. console.log("Session UUID:", end_sess_uuid);
  216.  
  217. //Fetch the end page
  218. const test_end_text = await fetch(`https://${hostname}/test/complete/${end_sess_uuid}`, {
  219. redirect: 'follow'
  220. }).then(res => res.text());
  221.  
  222. //Get the text end document
  223. const test_end_document = document.createElement("html");
  224. test_end_document.innerHTML = test_end_text;
  225.  
  226. //Get the answers
  227. const answers = test_end_document.querySelector(".homework-stats");
  228. console.log("Answer element:", answers);
  229.  
  230. //Display answers
  231. display_answers(answers);
  232. console.log("Answers displayed");
  233.  
  234. //Stop homework
  235. await fetch(`https://${hostname}/test/homework/${homework_id}/stop`, {
  236. method: 'POST',
  237. credentials: 'include',
  238. body: homework_csrf_form_data,
  239. });
  240. console.log("Homework stopped");
  241.  
  242. //Delete homework
  243. await fetch(`https://${hostname}/test/homework/${homework_id}/delete`, {
  244. method: 'POST',
  245. credentials: 'include',
  246. body: homework_csrf_form_data,
  247. });
  248. console.log("Homework deleted");
  249.  
  250. console.log("DONE");
  251.  
  252. return answers
  253. };
  254.  
  255. function pre_display() {
  256. //Delete previously displayed
  257. Array.from(document.querySelectorAll(".question-view-item")).forEach(item => item.remove());
  258.  
  259. //Clear the regular questions
  260. Array.from(document.querySelectorAll(".answer-sheet")).forEach(item => item.remove());
  261. }
  262.  
  263. function display_answers(answers) {
  264. pre_display()
  265.  
  266. //Add classes
  267. answers.classList.add("row");
  268. answers.classList.add("answer-sheet")
  269.  
  270. //HACK: Remove "- your answer" text
  271. answers.innerHTML = answers.innerHTML.replaceAll("<em>— ваша відповідь</em>", "");
  272.  
  273. //Add answers to the page
  274. const afer_element = document.querySelector(".block-head");
  275. afer_element.parentNode.insertBefore(answers, afer_element.nextSibling);
  276. };
  277.  
  278. async function loadStuffWriteCache() {
  279. const answers = await loadStuff();
  280. const elem = document.createElement("div");
  281. display_answers(elem);
  282. elem.appendChild(answers.cloneNode(true));
  283. (window.GM_setValue ?? localStorage.setItem.bind(localStorage))(ls_key, elem.innerHTML);
  284. }
  285.  
  286. //Preload images
  287. const images = []
  288. {
  289. window._cow_taxes = images; //keep the reference alive
  290. images["loading"] = new Image();
  291. images["loading"].src = LOADING_IMAGE;
  292. images["test"] = new Image();
  293. images["test"].src = TEST_IMAGE_URL;
  294. images["update"] = new Image();
  295. images["update"].src = TEST_UPDATE_IMAGE_URL;
  296. images["error"] = new Image();
  297. images["error"].src = ERROR_IMAGE_URL;
  298. }
  299.  
  300. function loadErrorHandler(err) {
  301. console.error(err);
  302. const btn = document.querySelector(".clicky-click");
  303. btn.querySelector("img").replaceWith(images.error);
  304. const label = btn.querySelector("span")
  305. label.textContent = "Помилка\n"+err.toString()+"\n";
  306. label.innerHTML = label.innerHTML.replaceAll("\n", "<br>");
  307. label.innerHTML += `
  308. FIX 1 - Натисни кнопку ще раз<br>
  309. FIX 2 - <a href='https://naurok.com.ua/test/join?gamecode=0' target="_blank">Відкрий і закрий цю сторінку</a><br>
  310. FIX 3 - <a href='https://naurok.com.ua/login' target="_blank">Увійди в аккаунт Наурок</a><br>
  311. FIX 4 - Спробуй ще раз через 5 хвилин
  312. `;
  313. }
  314.  
  315. async function loadStuffAndWriteCacheWithErrorHandler() {
  316. try {
  317. await loadStuffWriteCache();
  318. return true;
  319. } catch(err) {
  320. loadErrorHandler(err);
  321. return false;
  322. }
  323. }
  324.  
  325. //Load cached answers
  326. let is_cached = false;
  327. let cached_element = null;
  328. if ((window.GM_getValue ?? localStorage.getItem.bind(localStorage))(ls_key, null)) {
  329. console.log("Cached found:", ls_key);
  330. const elem = document.createElement("div");
  331. try {
  332. elem.innerHTML = (GM_getValue ?? localStorage.getItem.bind(localStorage))(ls_key, null);
  333. cached_element = elem.firstChild;
  334. is_cached = true;
  335. (cached_element.querySelector(".chicken-beef")?.classList.add("answer-sheet")) && console.warn("Cache contains chicken beef");
  336. } catch (e) {
  337. console.error("Cache invalid:", e);
  338. is_cached = false;
  339. cached_element = null;
  340. }
  341. }
  342.  
  343. const MAIN = async () => {
  344. //Display cached stuff
  345. if (is_cached) {
  346. try {
  347. display_answers(cached_element);
  348. console.log("Cached answer displayed!")
  349. } catch(e) {
  350. console.error("Cache invalid:", e);
  351. is_cached = false;
  352. cached_element = null;
  353. }
  354. }
  355.  
  356. //Add CSS
  357. {
  358. const style = `
  359.  
  360. .answer-sheet {
  361. padding: 1.33rem;
  362. }
  363.  
  364. /* style indicator colors*/
  365. .answer-sheet .homework-stat-option-value.incorect :is(.quiz,.multiquiz) {
  366. background: #cccccc !important;
  367. color: black !important;
  368. }
  369. .answer-sheet .homework-stat-option-value.correct :is(.quiz,.multiquiz) {
  370. background: #23c552 !important;
  371. color: black !important;
  372. }
  373.  
  374. /* fix indicator offset */
  375. .answer-sheet .homework-stat-option-value :is(.quiz,.multiquiz) {
  376. top: .5rem !important;
  377. }
  378.  
  379. /* Green bg on correct*/
  380. .answer-sheet .homework-stat-option-value.correct {
  381. background: linear-gradient(to bottom, rgba(0,0,0,0), #e3f7e9 15%, #e3f7e9 85%, rgba(0,0,0,0)) !important;
  382. border-radius: .25rem !important;
  383. }
  384.  
  385. /* fix uneven p padding*/
  386. .answer-sheet .homework-stat-option-value p {
  387. margin: 0.5rem 0.25rem !important;
  388. }
  389.  
  390. /* hide q ranking */
  391. .answer-sheet .question-label {
  392. display: none !important;
  393. }
  394.  
  395. /* hide weird orange overlays */
  396. .answer-sheet .ql-cursor {
  397. display: none !important;
  398. }
  399.  
  400. /* hide correctness indicators*/
  401. .answer-sheet .content-block.success,
  402. .answer-sheet .content-block.skipped,
  403. .answer-sheet .content-block.failed,
  404. .answer-sheet .content-block.partial {
  405. border-left: none !important;
  406. }
  407.  
  408. /* style the main button */
  409. .clicky-click {
  410. display: flex;
  411. width: 100%;
  412. border-width: 0;
  413. font-family: inherit;
  414. font-size: inherit;
  415. font-style: inherit;
  416. font-weight: inherit;
  417. line-height: inherit;
  418. margin-bottom: 0 !important;
  419. }
  420. #cb_wrapper {
  421. display: blck;
  422. text-align: center;
  423. }
  424. #auto_load_cb {
  425. margin-right: .25rem;
  426. }
  427. #auto_load_cb ~ label {
  428. font-weight: unset;
  429. }
  430.  
  431. /* Use flex to style our button */
  432. .test-action-button.clicky-click {
  433. display: flex !important;
  434. flex-direction: column !important;
  435. height: unset !important;
  436. gap: 10px !important;
  437. }
  438. .test-action-button.clicky-click * {
  439. position: unset !important;
  440. }
  441.  
  442. /* This applies to ALL buttons (makes them a bit fancier because why not) */
  443. .test-action-button {
  444. transition: all .25s !important;
  445. border: 1px solid rgba(0,0,0,.1) !important;
  446. border-radius: 10px !important;
  447. }
  448. .test-action-button:hover {
  449. background: #f0f0f0 !important;
  450. }
  451. .test-action-button:hover > * {
  452. filter: drop-shadow(0px 0px 4px #dddddd);
  453. }
  454. .test-action-button > img {
  455. transition: transform .25s !important;
  456. }
  457. .test-action-button:not(:disabled):hover > img {
  458. transform: scale(0.9) rotate(-3deg);
  459. }
  460.  
  461. /* todo: style auto load checkbox */
  462. #auto_load_cb {}
  463.  
  464. /* fix scrolling */
  465. body {
  466. overflow: auto !important;
  467. }
  468.  
  469. /* Remove ads */
  470. /* TODO: fix! hides answers too */
  471. /*.col-sm-8.col-md-9 {
  472. display: none !important;
  473. }*/
  474.  
  475. /* Fix em coloring */
  476. .homework-stat-option-value em {
  477. color: inherit !important;
  478. }
  479. `;
  480. const style_elem = document.createElement("style");
  481. style_elem.textContent = style;
  482. document.head.appendChild(style_elem);
  483. }
  484.  
  485. //Create answers button
  486. {
  487. const button = document.createElement("button");
  488. button.type = "button";
  489. button.classList.add("test-action-button");
  490. button.classList.add("clicky-click");
  491. button.appendChild(images.test);
  492. const text_elem = document.createElement("span");
  493. text_elem.textContent = "Завантажити відповіді";
  494. button.appendChild(text_elem);
  495. if (is_cached) {
  496. button.querySelector('img').replaceWith(images.update);
  497. text_elem.textContent = "Оновити відповіді";
  498. }
  499. button.addEventListener("click", async () => {
  500. button.querySelector('img').replaceWith(images.loading);
  501. text_elem.textContent = "Завантаження...";
  502. button.disabled = true;
  503. if (await loadStuffAndWriteCacheWithErrorHandler()) {
  504. button.querySelector('img').replaceWith(images.update);
  505. text_elem.textContent = "Оновити відповіді";
  506. }
  507. button.disabled = false;
  508. });
  509. const buttons = document.querySelector(".single-test-actions");
  510. buttons.prepend(button);
  511. }
  512.  
  513. //Create auto load toggle
  514. {
  515. //Create checkbox
  516. const auto_load_cb = document.createElement("input");
  517. auto_load_cb.id = "auto_load_cb";
  518. auto_load_cb.type = "checkbox";
  519. const save_state = () => {
  520. (window.GM_setValue || localStorage.setItem.bind(localStorage))("auto-load", auto_load_cb.checked ? "1" : "0");
  521. }
  522. auto_load_cb.checked = ((window.GM_getValue || localStorage.getItem.bind(localStorage))("auto-load") || "0") == "1";
  523. save_state();
  524. auto_load_cb.addEventListener("change", save_state);
  525. if (!is_cached && auto_load_cb.checked) {
  526. document.querySelector(".clicky-click").click();
  527. }
  528. //Add it
  529. const cb_wrapper = document.createElement("div");
  530. cb_wrapper.id = "cb_wrapper";
  531. const cb_label = document.createElement("label");
  532. cb_label.textContent = "Автоматично завантажувати відповіді";
  533. cb_label.setAttribute("for", auto_load_cb.id);
  534. cb_wrapper.appendChild(auto_load_cb);
  535. cb_wrapper.appendChild(cb_label);
  536. const afer_element = document.querySelector(".clicky-click");
  537. afer_element.parentNode.insertBefore(cb_wrapper, afer_element.nextSibling);
  538. }
  539. };
  540.  
  541. // Run if loaded late
  542. if ((document?.readyState == "interactive") || (document?.readyState == "complete")) {
  543. MAIN();
  544. } else {
  545. document.addEventListener("DOMContentLoaded", MAIN);
  546. }
  547.  
  548. }