Naurok Bypass v2

Fetches answers to *all* Naurok quizes

目前為 2023-04-26 提交的版本,檢視 最新版本

// ==UserScript==
// @name        Naurok Bypass v2
// @author      griffi-gh
// @namespace   griffi-gh
// @description Fetches answers to *all* Naurok quizes
// @version     5.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        data:image/jpeg;base64,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
// @homepageURL https://gf.qytechs.cn/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";

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
  const 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);

  //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));
  (GM_setValue ?? localStorage.setItem)(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;
  }
}

//Migrate answers to GM storage
if (window.GM_setValue && window.GM_getValue) {
  for (let i = 0; i < localStorage.length; i++) {
    const key = localStorage.key(i)
    const value = localStorage.getItem(key);
    if (!key.startsWith("cached-kettle-smoke")) {
      continue;
    }
    console.log(`Moving "${key}" from localStorage`);
    const newkey = key.replace("cached-kettle-smoke", "cached");
    if (!GM_getValue(newkey, false)) {
      GM_setValue(newkey, value);
    }
    localStorage.removeItem(key);
  }
}

//Load cached answers
let is_cached = false;
let cached_element = null;
if ((window.GM_getValue ?? localStorage.getItem)(ls_key, null)) {
  console.log("Cached found:", ls_key);
  const elem = document.createElement("div");
  try {
    elem.innerHTML = (GM_getValue ?? localStorage.getItem)(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;
  }
}

document.addEventListener("DOMContentLoaded", 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;
      }
      #library .answer-sheet .quiz.incorect,
      #library .answer-sheet .multiquiz.incorect,
      #library .answer-sheet .homework-stat-option-value.incorect span {
        background: #d8d8d8 !important;
      }
      #library .answer-sheet .quiz.correct,
      #library .answer-sheet .multiquiz.correct,
      #library .answer-sheet .homework-stat-option-value.correct span {
        background: #66bb6a !important;
      }
      .answer-sheet .content-block .question-label {
        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 {
        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);
      }

      /* style auto load checkbox */
      #auto_load_cb {

      }
    `;
    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)("auto-load", auto_load_cb.checked ? "1" : "0");
    }
    auto_load_cb.checked = ((window.GM_getValue || localStorage.getItem)("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);
  }
});

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址