Udemy - Copy from Practice Test

Copy questions and answers from Udemy practice exams with ease

// ==UserScript==
// @name         Udemy - Copy from Practice Test
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Copy questions and answers from Udemy practice exams with ease
// @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) {
    // if mutation is caused by our added button elements return to avoid infinite recursion
    if (
      mutationsList.find(
        (el) => el.addedNodes[0]?.id === "userscript-added-button"
      )
    ) {
      return;
    }

    const questionSections = Array.from(
      document.querySelectorAll(
        'div[class^="result-pane--question-result-pane-wrapper"]'
      )
    );
    questionSections.forEach((el) => {
      // if button already added to the question/answer form return
      if (el.querySelector("#userscript-added-button")) return;

      const question = el.querySelector("#question-prompt").textContent.trim();

      const answerSection = el.querySelector(
        'div[class^="result-pane--question-result-pane-expanded-content"]'
      );

      const allAnswers = Array.from(
        answerSection.querySelectorAll(
          'div[class^="answer-result-pane--answer-body"]'
        )
      )
        .map((el) => el.textContent.trim())
        .join("\n\n");

      const correctAnswers = Array.from(
        answerSection.querySelectorAll(
          'div[class^="answer-result-pane--answer-correct"]'
        )
      )
        .map((el) => {
          return el
            .querySelector('div[class^="answer-result-pane--answer-body"]')
            .textContent.trim();
        })
        .join("\n\n");

      const explanation = el
        .querySelector("#overall-explanation")
        ?.textContent.trim();

      const copyQuestionButton = document.createElement("button");
      copyQuestionButton.setAttribute("id", "userscript-added-button");
      copyQuestionButton.innerHTML = "Copy Question";

      copyQuestionButton.addEventListener("click", () => {
        navigator.clipboard.writeText(question + "\n\n" + allAnswers);
      });

      const copyAnswerButton = document.createElement("button");
      copyAnswerButton.setAttribute("id", "userscript-added-button");
      copyAnswerButton.innerHTML = "Copy Answer";

      copyAnswerButton.addEventListener("click", () => {
        navigator.clipboard.writeText(correctAnswers + "\n\n" + explanation);
      });

      el.append(copyQuestionButton);
      el.append(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);
})();