ED-Puzzle Hack

An Ed-Puzzle Hack with a LOT of Features!!!!!!!

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(Tôi đã có Trình quản lý tập lệnh người dùng, hãy cài đặt nó!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         ED-Puzzle Hack
// @namespace    https://github.com/longkidkoolstar
// @description  An Ed-Puzzle Hack with a LOT of Features!!!!!!!
// @author       longkidkoolstar
// @version      0.2.1
// @icon         https://th.bing.com/th/id/OIP.3LKllA9fA7DTJ4Kb92LbowHaHa?rs=1&pid=ImgDetMain
// @match        *://edpuzzle.com/lti/*
// @match        *://edpuzzle.com/assignments/*
// @match        *://edpuzzle.com/media/*
// @match        *://youtube.com/embed*
// @match        *://youtube-nocookie.com/embed*
// @grant        none
// @license      GPL
// ==/UserScript==

/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

//Copyright (C) [2024] [longkidkoolstar]
// This script is based on the original code from the edpuzzle-answers project by ading2210
// See: https://github.com/ading2210/edpuzzle-answers



var popup = null;
var base_url;
if (typeof document.dev_env != "undefined") {
  base_url = document.dev_env;
}
else {
  //get resources off of github to not inflate the jsdelivr stats
  base_url = "https://raw.githubusercontent.com/ading2210/edpuzzle-answers/main";
}

function http_get(url, callback, headers=[], method="GET", content=null) {
  var request = new XMLHttpRequest();
  request.addEventListener("load", callback);
  request.open(method, url, true);

  if (window.__EDPUZZLE_DATA__ && window.__EDPUZZLE_DATA__.token) {
    headers.push(["authorization", window.__EDPUZZLE_DATA__.token]);
  }
  for (const header of headers) {
    request.setRequestHeader(header[0], header[1]);
  }

  request.send(content);
}
function createActivationButton() {
  // Create a button element
  var activationButton = document.createElement("button");

  // Set the button text
  activationButton.textContent = "Activate Edpuzzle Script";

  // Add a click event listener to the button
  activationButton.addEventListener("click", function () {
    // Call the init function to activate the Edpuzzle script
    init();
  });

  // Create a style for the button
  var buttonStyle = `
background-color: #00ADEF;
color: #FFFFFF;
padding: 10px 20px;
font-size: 16px;
border: none;
cursor: pointer;
transition: opacity 0.3s ease-in-out; /* Add a smooth transition effect */

  `;

  // Apply the style to the button
  activationButton.style.cssText = buttonStyle;

  // Add a hover effect
  activationButton.addEventListener("mouseover", function () {
    activationButton.style.opacity = 0.8; // Adjust the opacity as needed
  });

  // Restore the original opacity on mouseout
  activationButton.addEventListener("mouseout", function () {
    activationButton.style.opacity = 1;
  });

  // Append the button to the body of the document
  document.body.appendChild(activationButton);
}


// Call the function to create the activation button
createActivationButton();


function init() {
  if (window.location.hostname == "edpuzzle.hs.vc") {
    alert("To use this, drag this button into your bookmarks bar. Then, run it when you're on an Edpuzzle assignment.");
  }
  else if ((/https{0,1}:\/\/edpuzzle.com\/assignments\/[a-f0-9]{1,30}\/watch/).test(window.location.href)) {
    getAssignment();
  }
  else if (window.canvasReadyState) {
    handleCanvasURL();
  }
  else if (window.schoologyMoreLess) {
    handleSchoologyURL();
  }
  else {
    alert("Please run this script on an Edpuzzle assignment. For reference, the URL should look like this:\nhttps://edpuzzle.com/assignments/{ASSIGNMENT_ID}/watch");
  }
}

function handleCanvasURL() {
  let location_split = window.location.href.split("/");
  let url = `/api/v1/courses/${location_split[4]}/assignments/${location_split[6]}`;
  http_get(url, function(){
    let data = JSON.parse(this.responseText);
    let url2 = data.url;

    http_get(url2, function() {
      let data = JSON.parse(this.responseText);
      let url3 = data.url;

      alert(`Please re-run this script in the newly opened tab. If nothing happens, then allow popups on Canvas and try again.`);
      open(url3);
    });
  });
}

function handleSchoologyURL() {
  let assignment_id = window.location.href.split("/")[4];
  let url = `/external_tool/${assignment_id}/launch/iframe`;
  http_get(url, function() {
    alert(`Please re-run this script in the newly opened tab. If nothing happens, then allow popups on Schoology and try again.`);

    //strip js tags from response and add to dom
    let html = this.responseText.replace(/<script[\s\S]+?<\/script>/, "");
    let div = document.createElement("div");
    div.innerHTML = html;
    let form = div.querySelector("form");

    let input = document.createElement("input")
    input.setAttribute("type", "hidden");
    input.setAttribute("name", "ext_submit");
    input.setAttribute("value", "Submit");
    form.append(input);
    document.body.append(div);

    //submit form in new tab
    form.setAttribute("target", "_blank");
    form.submit();
    div.remove();
  });
}

function getAssignment(callback) {
  var assignment_id = window.location.href.split("/")[4];
  if (typeof assignment_id == "undefined") {
    alert("Error: Could not infer the assignment ID. Are you on the correct URL?");
    return;
  }
  var url1 = "https://edpuzzle.com/api/v3/assignments/"+assignment_id;

  http_get(url1, function(){
    var assignment = JSON.parse(this.responseText);
    if ((""+this.status)[0] == "2") {
      openPopup(assignment);
    }
    else {
      alert(`Error: Status code ${this.status} recieved when attempting to fetch the assignment data.`)
    }
  });
}

function openPopup(assignment) {
  var media = assignment.medias[0];
  var teacher_assignment = assignment.teacherAssignments[0];
  var assigned_date = new Date(teacher_assignment.preferences.startDate);
  var date = new Date(media.createdAt);
  thumbnail = media.thumbnailURL;
  if (thumbnail.startsWith("/")) {
    thumbnail = "https://"+window.location.hostname+thumbnail;
  }

  var deadline_text;
  if (teacher_assignment.preferences.dueDate == "") {
    deadline_text = "no due date"
  }
  else {
    deadline_text = "due on "+(new Date(teacher_assignment.preferences.dueDate)).toDateString();
  }

  var base_html = `
  <!DOCTYPE html>
  <head>
    <style>
      * {font-family: Arial}
    </style>
    <script>
      var base_url = "${base_url}";
      function http_get(url, callback) {
        var request = new XMLHttpRequest();
        request.addEventListener("load", callback);
        request.open("GET", url, true);
        request.send();
      }
      function get_tag(tag, url) {
        console.log("Loading "+url);
        http_get(url, function(){
          if ((""+this.status)[0] == "2") {
            var element = document.createElement(tag);
            element.innerHTML = this.responseText;
            document.getElementsByTagName("head")[0].appendChild(element);
          }
          else {
            console.error("Could not fetch "+url);
          }
        });
      }
      get_tag("style", base_url+"/app/popup.css");
      get_tag("script", base_url+"/app/popup.js");
      get_tag("script", base_url+"/app/videooptions.js");
      get_tag("script", base_url+"/app/videospeed.js");
    </script>
    <title>Answers for: ${media.title}</title>
  </head>
  <div id="header_div">
    <div>
      <img src="${thumbnail}" height="108px">
    </div>
    <div id="title_div">
      <p style="font-size: 16px"><b>${media.title}</b></h2>
      <p style="font-size: 12px">Uploaded by ${media.user.name} on ${date.toDateString()}</p>
      <p style="font-size: 12px">Assigned on ${assigned_date.toDateString()}, ${deadline_text}</p>
      <p style="font-size: 12px">Correct choices are <u>underlined</u>.</p>
      <input id="skipper" type="button" value="Skip Video" onclick="skip_video();" disabled/>
      <input id="answers_button" type="button" value="Answer Questions" onclick="answer_questions();" disabled/>
      <div id="speed_container" hidden>
        <label style="font-size: 12px" for="speed_dropdown">Video speed:</label>
        <select name="speed_dropdown" id="speed_dropdown" onchange="video_speed()">
          <option value="0.25">0.25</option>
          <option value="0.5">0.5</option>
          <option value="0.75">0.75</option>
          <option value="1" selected>Normal</option>
          <option value="1.25">1.25</option>
          <option value="1.5">1.5</option>
          <option value="1.75">1.75</option>
          <option value="2">2</option>
          <option value="-1">Custom</option>
        </select>
        <label id="custom_speed_label" style="font-size: 12px" for="custom_speed"></label>
        <input type="range" id="custom_speed" name="custom_speed" value="1" min="0.1" max="16" step="0.1" oninput="video_speed()" hidden>
      </div>
      <div id="options_container">
        <label for="pause_on_focus" style="font-size: 12px">Don't pause on unfocus: </label>
        <input type="checkbox" id="pause_on_focus" name="pause_on_focus" onchange="toggle_unfocus();">
      </div>
    </div>
  </div>
  <hr>
  <div id="content">
    <p style="font-size: 12px" id="loading_text"></p>
  </div>
  <hr>
 `;
  popup = window.open("about:blank", "", "width=600, height=400");
  popup.document.write(base_html);

  popup.document.assignment = assignment;
  popup.document.dev_env = document.dev_env;
  popup.document.edpuzzle_data = window.__EDPUZZLE_DATA__;

  getMedia(assignment);
}

function getMedia(assignment) {
  var text = popup.document.getElementById("loading_text");
  text.innerHTML = `Fetching assignments...`;

  var media_id = assignment.teacherAssignments[0].contentId;
  var url2 = `https://edpuzzle.com/api/v3/media/${media_id}`;

  fetch(url2, {credentials: "omit"})
    .then(response => {
      if (!response.ok) {
        var text = popup.document.getElementById("loading_text");
        var content = popup.document.getElementById("content");
        popup.document.questions = questions;
        text.remove();
        content.innerHTML += `Error: Status code ${response.status} received when attempting to fetch the answers.`;
      }
      else return response.json();
    })
    .then(media => {
      parseQuestions(media.questions);
    })
}

function parseQuestions(questions) {
  var text = popup.document.getElementById("loading_text");
  var content = popup.document.getElementById("content");
  popup.document.questions = questions;
  text.remove();

  if (questions == null) {
    content.innerHTML += `<p style="font-size: 12px">Error: Could not get the media for this assignment. </p>`;
    return;
  }

  var question;
  var counter = 0;
  var counter2 = 0;
  for (let i=0; i<questions.length; i++) {
    for (let j=0; j<questions.length-i-1; j++) {
      if (questions[j].time > questions[j+1].time){
       let question_old = questions[j];
       questions[j] = questions[j + 1];
       questions[j+1] = question_old;
     }
    }
  }

  for (let i=0; i<questions.length; i++) {
    question = questions[i];
    let choices_lines = [];

    if (typeof question.choices != "undefined") {
      let min = Math.floor(question.time/60).toString();
      let secs = Math.floor(question.time%60).toString();
      if (secs.length == 1) {
        secs = "0"+secs;
      }
      let timestamp = min+":"+secs;
      let question_content;
      if (question.body[0].text != "") {
        question_content = `<p>${question.body[0].text}</p>`;
      }
      else {
        question_content = question.body[0].html;
      }

      let answer_exists = false;
      for (let j=0; j<question.choices.length; j++) {
        let choice = question.choices[j];
        if (typeof choice.body != "undefined") {
          counter++;
          let item_html;
          if (choice.body[0].text != "") {
            item_html = `<p>${choice.body[0].text}</p>`;
          }
          else {
            item_html = `${choice.body[0].html}`;
          }
          if (choice.isCorrect == true) {
            choices_lines.push(`<li class="choice choice-correct">${item_html}</li>`);
            answer_exists = true;
          }
          else {
            choices_lines.push(`<li class="choice">${item_html}</li>`);
          }
        }
      }
      if (!answer_exists) continue;

      let choices_html = choices_lines.join("\n");
      let table = ``
      if (counter2 != 0) {
        table += `<hr>`;
      }
      table += `
      <table>
        <tr class="header no_vertical_margin">
          <td class="timestamp_div no_vertical_margin">
            <p>[${timestamp}]</p>
          </td>
          <td class="question">
            ${question_content}
          </td>
        </tr>
        <tr>
          <td></td>
          <td>
            <ul style="margin-top: 6px; margin-bottom: 0px; padding-left: 18px;">
              ${choices_html}
            </ul>
          </td>
        </tr>
      </table>
      `;

      content.innerHTML += table;
      counter2++;
    }
  }
  popup.document.getElementById("skipper").disabled = false;
  if (counter == 0 || counter2 == 0) {
    content.innerHTML += `<p style="font-size: 12px">No valid multiple choice questions were found.</p>`;
  }
  else {
    popup.document.getElementById("answers_button").disabled = false;
  }
  popup.questions = questions;
}