// ==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;
}