Naurok Bypass v2

Fetches answers to *all* Naurok quizes

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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
// @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
// ==/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
const SESSION_PROXY = "https://eobzz8g6oxzrky0.m.pipedream.net/";

const ls_key = `cached-${document.location.pathname.replaceAll("/", "-").slice(1, -5).toLowerCase()}`;

async function loadStuff() {
  pre_display();
  const hostname = window.location.hostname;
  const base = document.location.href.slice(0, -5);
  
  const set_text = await fetch(base + "/set").then(x => x.text());
  const set_document = document.createElement("html");
  set_document.innerHTML = set_text;
  
  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");
  
  const homework_res = await fetch(set_form.action, {
    redirect: 'follow',
    method: 'POST',
    credentials: 'include',
    body: set_form_data,
  });

  const homework_url = homework_res.url;
  const homework_id = homework_url.split("/").at(-1);
  const homework_text = await homework_res.text();
  const homework_document = document.createElement("html");
  homework_document.innerHTML = homework_text;
  
  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);
  
  const homework_code = homework_document.querySelector(".homework-code").textContent;
  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;
  
  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);
  
  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",
    },
  });
  
  const test_session_url = join_res.url;
  const session_text = await fetch(test_session_url).then(res => res.text());
  const session_document = document.createElement("html");
  session_document.innerHTML = session_text;
  
  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;
  
  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());
  
  if (session_info == false) {
    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());
  }
  
  const {latest_question, questions} = session_info;
  const question = latest_question ? questions.find(question => question.id == latest_question) : questions[0];
  const answer_id = question.options[0].id.toString();
  
  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
    }),
  });
  
  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;
  
  const test_end_text = await fetch(`https://${hostname}/test/complete/${end_sess_uuid}`, {
    redirect: 'follow'
  }).then(res => res.text());
  
  const test_end_document = document.createElement("html");
  test_end_document.innerHTML = test_end_text;
  const answers = test_end_document.querySelector(".homework-stats");
  display_answers(answers);
  
  await fetch(`https://${hostname}/test/homework/${homework_id}/stop`, {
    method: 'POST',
    credentials: 'include',
    body: homework_csrf_form_data,
  });
  
  await fetch(`https://${hostname}/test/homework/${homework_id}/delete`, {
    method: 'POST',
    credentials: 'include',
    body: homework_csrf_form_data,
  });
  
  return answers;
};

function pre_display() {
  Array.from(document.querySelectorAll(".question-view-item")).forEach(item => item.remove());
  Array.from(document.querySelectorAll(".answer-sheet")).forEach(item => item.remove());
}

function display_answers(answers) {
  pre_display();
  answers.classList.add("row");
  answers.classList.add("answer-sheet");
  answers.innerHTML = answers.innerHTML.replaceAll("<em>— ваша відповідь</em>", "");
  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);
}

function loadErrorHandler(err) {
  console.error(err);
  const btn = document.querySelector(".clicky-click");
  btn.title = `Помилка: ${err.toString()}\nСпробуйте:\n1. Натиснути кнопку ще раз\n2. Відкрити і закрити сторінку https://naurok.com.ua/test/join?gamecode=0\n3. Увійти в аккаунт Наурок https://naurok.com.ua/login\n4. Спробувати ще раз через 5 хвилин`;
}

async function loadStuffAndWriteCacheWithErrorHandler() {
  try {
    await loadStuffWriteCache();
    return true;
  } catch(err) {
    loadErrorHandler(err);
    return false;
  }
}

let is_cached = false;
let cached_element = null;
if ((window.GM_getValue ?? localStorage.getItem.bind(localStorage))(ls_key, null)) {
  const elem = document.createElement("div");
  try {
    elem.innerHTML = (GM_getValue ?? localStorage.getItem.bind(localStorage))(ls_key, null);
    cached_element = elem.firstChild;
    is_cached = true;
  } catch (e) {
    console.error("Cache invalid:", e);
    is_cached = false;
    cached_element = null;
  }
}

const MAIN = async () => {
  if (is_cached) {
    try {
      display_answers(cached_element);
    } catch(e) {
      console.error("Cache invalid:", e);
      is_cached = false;
      cached_element = null;
    }
  }

  const style = `
    .answer-sheet {
      padding: 1.33rem;
    }
    .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;
    }
    .answer-sheet .homework-stat-option-value :is(.quiz,.multiquiz) {
      top: .5rem !important;
    }
    .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;
    }
    .answer-sheet .homework-stat-option-value p {
      margin: 0.5rem 0.25rem !important;
    }
    .answer-sheet .question-label {
      display: none !important;
    }
    .answer-sheet .ql-cursor {
      display: none !important;
    }
    .answer-sheet .content-block.success,
    .answer-sheet .content-block.skipped,
    .answer-sheet .content-block.failed,
    .answer-sheet .content-block.partial {
      border-left: none !important;
    }
    .clicky-click {
      background: #808080;
      color: white;
      padding: 10px 20px;
      border: none;
      border-radius: 5px;
      cursor: pointer;
      transition: background 0.25s;
      width: 100px;
      height: 40px;
    }
    .clicky-click:hover {
      background: #666666;
    }
    .clicky-click:disabled {
      background: #999999;
      cursor: not-allowed;
    }
    #cb_wrapper {
      display: block;
      text-align: center;
      margin-top: 10px;
    }
    #auto_load_cb {
      margin-right: .25rem;
    }
    body {
      overflow: auto !important;
    }
    .homework-stat-option-value em {
      color: inherit !important;
    }
  `;
  
  const style_elem = document.createElement("style");
  style_elem.textContent = style;
  document.head.appendChild(style_elem);

  const button = document.createElement("button");
  button.type = "button";
  button.classList.add("clicky-click");
  
  button.addEventListener("click", async () => {
    button.disabled = true;
    await loadStuffAndWriteCacheWithErrorHandler();
    button.disabled = false;
  });
  
  const buttons = document.querySelector(".single-test-actions");
  buttons.prepend(button);

  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) {
    button.click();
  }
  
  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);
};

if ((document?.readyState == "interactive") || (document?.readyState == "complete")) {
  MAIN();
} else {
  document.addEventListener("DOMContentLoaded", MAIN);
}

}