- // ==UserScript==
- // @name EdPuzzle+
- // @namespace https://github.com/Enchoseon/better-edpuzzle-userscript/raw/main/better-edpuzzle-userscript.user.js
- // @version 0.7.2
- // @description You can skip forward or skip directly to the questions
- // @author Me
- // @include *edpuzzle.com/lti/*
- // @include *edpuzzle.com/assignments/*
- // @include *edpuzzle.com/media/*
- // @include *youtube.com/embed*
- // @include *youtube-nocookie.com/embed*
- // @run-at document-start
- // @grant none
- // ==/UserScript==
-
- (function() {
- 'use strict';
- const config = { // Edit this variable to set your own settings!
- "speed": 2,
- }
- // =========
- // Speedhack
- // =========
- function speedhack(mediaSource) {
- console.log("Media Source: " + mediaSource);
- switch(mediaSource) {
- case "youtube":
- break;
- case "vimeo":
- break;
- case "vimeo_with_controls":
- break;
- case "edpuzzle":
- break;
- case "file":
- break;
- default:
- }
- }
- if (window != window.top && (window.location.href.includes("youtube.com/embed/") || window.location.href.includes("youtube-nocookie.com/embed/")) && window.location.href.includes("origin=https://edpuzzle.com")) { // YouTube speedhack (sticking this in the speedhack function would require a bunch of postMessage BS to get around CORs. Wish I could just grant myself the permission to access the iframe (e.g. document.getElementsByTagName("iframe")[0].contentWindow.document.getElementsByTagName("video")[0].playbackRate = config.speed;))
- window.addEventListener("click", function() {
- console.log(window.location.href);
- document.getElementsByTagName("video")[0].playbackRate = config.speed;
- });
- }
- // ===============
- // Anti Auto-Pause
- // ===============
- window.addEventListener("load", function() { // Make some properties always return a value of false
- const propArr = ["hidden", "mozHidden", "msHidden", "webkitHidden"];
- for (var i = 0; i < propArr.length; i++) {
- Object.defineProperty(document, propArr[i], function() {
- value : false
- });
- }
- });
- // =========
- // API Stuff
- // =========
- function interceptAPICall(responseText) { // Read the API call for speedhacking (& debugging).
- responseText = JSON.parse(responseText);
- console.log("API Call Intercepted:");
- console.log(responseText);
- var mediaSource = null;
- if (responseText.medias) {
- mediaSource = responseText.medias[0].source;
- } else if (responseText.source) {
- mediaSource = responseText.source;
- }
- window.addEventListener("load", speedhack(mediaSource));
- }
- function modifyAPICall(responseText) { // Modify the API call to allow skipping.
- // Occasionally the API returns a responses w/ stuff in different locations—since there isn't actual documentation, it's kind of hard to understand—so we're going to inefficiently brute force it instead
- return responseText.replace(`"allowSkipAhead":false`, `"allowSkipAhead":true`);
- }
- function getAPIPath() { // Get the API path
- var urlPath = window.location.pathname;
- var apiPath;
- if (urlPath.startsWith("/assignments/")) {
- var id = urlPath.replace("/assignments/", "")
- .replace("/watch", "");
- apiPath = "/api/v3/assignments/" + id;
- } else if (urlPath.startsWith("/media/")) {
- var id = urlPath.replace("/media/", "");
- apiPath = "/api/v3/media/" + id;
- }
- return apiPath;
- }
- if (window.location.hostname === "edpuzzle.com") { // Intercept XMLHttpRequests (https://stackoverflow.com/a/41899308) (gets around one-time token for LMS & lets us modify the response)
- (function(window) {
- var OriginalXHR = XMLHttpRequest;
- var XHRProxy = function() {
- this.xhr = new OriginalXHR();
- function delegate(prop) {
- Object.defineProperty(this, prop, {
- get: function() {
- return this.xhr[prop];
- },
- set: function(value) {
- this.xhr.timeout = value;
- }
- });
- }
- delegate.call(this, "timeout");
- delegate.call(this, "responseType");
- delegate.call(this, "withCredentials");
- delegate.call(this, "onerror");
- delegate.call(this, "onabort");
- delegate.call(this, "onloadstart");
- delegate.call(this, "onloadend");
- delegate.call(this, "onprogress");
- };
- XHRProxy.prototype.open = function(method, url, async, username, password) {
- var ctx = this;
- function applyInterceptors(src) {
- ctx.responseText = ctx.xhr.responseText;
- for (var i = 0; i < XHRProxy.interceptors.length; i++) {
- var applied = XHRProxy.interceptors[i](method, url, ctx.responseText, ctx.xhr.status);
- if (applied !== undefined) {
- ctx.responseText = applied;
- }
- }
- }
- function setProps() {
- ctx.readyState = ctx.xhr.readyState;
- ctx.responseText = ctx.xhr.responseText;
- ctx.responseURL = ctx.xhr.responseURL;
- ctx.responseXML = ctx.xhr.responseXML;
- ctx.status = ctx.xhr.status;
- ctx.statusText = ctx.xhr.statusText;
- }
- this.xhr.open(method, url, async, username, password);
- this.xhr.onload = function(evt) {
- if (ctx.onload) {
- setProps();
-
- if (ctx.xhr.readyState === 4) {
- applyInterceptors();
- }
- return ctx.onload(evt);
- }
- };
- this.xhr.onreadystatechange = function (evt) {
- if (ctx.onreadystatechange) {
- setProps();
-
- if (ctx.xhr.readyState === 4) {
- applyInterceptors();
- }
- return ctx.onreadystatechange(evt);
- }
- };
- };
- XHRProxy.prototype.addEventListener = function(event, fn) {
- return this.xhr.addEventListener(event, fn);
- };
- XHRProxy.prototype.send = function(data) {
- return this.xhr.send(data);
- };
- XHRProxy.prototype.abort = function() {
- return this.xhr.abort();
- };
- XHRProxy.prototype.getAllResponseHeaders = function() {
- return this.xhr.getAllResponseHeaders();
- };
- XHRProxy.prototype.getResponseHeader = function(header) {
- return this.xhr.getResponseHeader(header);
- };
- XHRProxy.prototype.setRequestHeader = function(header, value) {
- return this.xhr.setRequestHeader(header, value);
- };
- XHRProxy.prototype.overrideMimeType = function(mimetype) {
- return this.xhr.overrideMimeType(mimetype);
- };
-
- XHRProxy.interceptors = [];
- XHRProxy.addInterceptor = function(fn) {
- this.interceptors.push(fn);
- };
-
- window.XMLHttpRequest = XHRProxy;
- XHRProxy.addInterceptor(function(method, url, responseText, status) {
- if (url === getAPIPath()) {
- interceptAPICall(responseText);
- return modifyAPICall(responseText);
- }
- });
- })(window);
- }
- })();