Udemy - Copy from Section Quiz

Easily copy questions and answers from Udemy section quizzes

// ==UserScript==
// @name         Udemy - Copy from Section Quiz
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Easily copy questions and answers from Udemy section quizzes
// @author       John Farrell (https://www.johnfarrell.dev/)
// @match        https://www.udemy.com/course/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=udemy.com
// ==/UserScript==

(function () {
  "use strict";

  // Select the node that will be observed for mutations
  const targetNode = document.querySelector("body");

  // Options for the observer (which mutations to observe)
  const config = { attributes: true, childList: true, subtree: true };

  const callback = function (mutationsList, observer) {
    // return if mutationList contains mutations caused by us adding buttons, otherwise get infinite recursion until browser crashes
    if (
      mutationsList.find(
        (el) =>
          el.addedNodes[0]?.id === "userscript-added-button-copy-question" ||
          el.addedNodes[0]?.id === "userscript-added-button-copy-answer"
      )
    ) {
      return;
    }

    const isQuizPage =
      document.querySelector(
        'div[class^="compact-quiz-container--compact-quiz-container--"]'
      ) !== null;
    if (!isQuizPage) {
      return;
    }

    const progressionButton = document.querySelector(
      'button[data-purpose="next-question-button"]'
    );

    if (!progressionButton) {
      return;
    }

    const isQuestionStep = progressionButton.textContent === "Check answer";
    const isAnswerStep =
      progressionButton.textContent === "Next" ||
      progressionButton.textContent === "See results";

    const quizFooter = document.querySelector(
      'div[class^="curriculum-item-footer--flex-align-center--"] > div'
    );

    if (isQuestionStep) {
      if (document.querySelector("#userscript-added-button-copy-question")) {
        return;
      }

      // remove the copy answer button added from isAnswerStep
      const copyAnswerButton = document.querySelector(
        "#userscript-added-button-copy-answer"
      );
      copyAnswerButton?.parentNode.removeChild(copyAnswerButton);

      const questionElement = document.querySelector("#question-prompt");
      const question = questionElement.innerText;

      const answerContainer = document.querySelector(
        'ul[aria-labelledby="question-prompt"]'
      );
      const answers = Array.from(answerContainer.querySelectorAll("li")).map(
        (el) => "\t• " + el.innerText
      );

      const copyText = question + "\n\n" + answers.join("\n");

      const copyQuestionButton = document.createElement("button");
      copyQuestionButton.setAttribute(
        "id",
        "userscript-added-button-copy-question"
      );
      copyQuestionButton.innerHTML = "Copy Question";
      copyQuestionButton.addEventListener("click", () => {
        navigator.clipboard.writeText(copyText);
      });

      quizFooter.append(copyQuestionButton);
    } else if (isAnswerStep) {
      if (document.querySelector("#userscript-added-button-copy-answer")) {
        return;
      }

      const answers = Array.from(document.querySelectorAll("input[type=radio]"))
        .filter((el) => el.checked)
        .map((el) => el.parentElement.textContent.trim())
        .join("\n\n");

      const additionalInfo =
        document
          .querySelector('div[class*="alert-banner-module--body--"]')
          ?.textContent.trim() || "";

      const copyText = additionalInfo
        ? answers + "\n\n" + additionalInfo
        : answers;

      const copyAnswerButton = document.createElement("button");
      copyAnswerButton.setAttribute(
        "id",
        "userscript-added-button-copy-answer"
      );
      copyAnswerButton.innerHTML = "Copy Answer";
      copyAnswerButton.addEventListener("click", () => {
        navigator.clipboard.writeText(copyText);
      });

      quizFooter.append(copyAnswerButton);

      const nextQuestionSelector =
        'button[data-purpose="next-question-button"]';
      document
        .querySelector(nextQuestionSelector)
        .addEventListener("click", () => {
          // remove the copy question button when we click to go to the next question
          const copyQuestionButton = document.querySelector(
            "#userscript-added-button-copy-question"
          );
          copyQuestionButton?.parentNode.removeChild(copyQuestionButton);

          const copyAnswerButton = document.querySelector(
            "#userscript-added-button-copy-answer"
          );
          copyAnswerButton?.parentNode.removeChild(copyAnswerButton);
        });
    }
  };

  // Create an observer instance linked to the callback function
  const observer = new MutationObserver(callback);

  // Start observing the target node for configured mutations
  observer.observe(targetNode, config);
})();