EdPuzzle+

You can skip forward or skip directly to the questions

  1. // ==UserScript==
  2. // @name EdPuzzle+
  3. // @namespace https://github.com/Enchoseon/better-edpuzzle-userscript/raw/main/better-edpuzzle-userscript.user.js
  4. // @version 0.7.2
  5. // @description You can skip forward or skip directly to the questions
  6. // @author Me
  7. // @include *edpuzzle.com/lti/*
  8. // @include *edpuzzle.com/assignments/*
  9. // @include *edpuzzle.com/media/*
  10. // @include *youtube.com/embed*
  11. // @include *youtube-nocookie.com/embed*
  12. // @run-at document-start
  13. // @grant none
  14. // ==/UserScript==
  15. (function() {
  16. 'use strict';
  17. const config = { // Edit this variable to set your own settings!
  18. "speed": 2,
  19. }
  20. // =========
  21. // Speedhack
  22. // =========
  23. function speedhack(mediaSource) {
  24. console.log("Media Source: " + mediaSource);
  25. switch(mediaSource) {
  26. case "youtube":
  27. break;
  28. case "vimeo":
  29. break;
  30. case "vimeo_with_controls":
  31. break;
  32. case "edpuzzle":
  33. break;
  34. case "file":
  35. break;
  36. default:
  37. }
  38. }
  39. 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;))
  40. window.addEventListener("click", function() {
  41. console.log(window.location.href);
  42. document.getElementsByTagName("video")[0].playbackRate = config.speed;
  43. });
  44. }
  45. // ===============
  46. // Anti Auto-Pause
  47. // ===============
  48. window.addEventListener("load", function() { // Make some properties always return a value of false
  49. const propArr = ["hidden", "mozHidden", "msHidden", "webkitHidden"];
  50. for (var i = 0; i < propArr.length; i++) {
  51. Object.defineProperty(document, propArr[i], function() {
  52. value : false
  53. });
  54. }
  55. });
  56. // =========
  57. // API Stuff
  58. // =========
  59. function interceptAPICall(responseText) { // Read the API call for speedhacking (& debugging).
  60. responseText = JSON.parse(responseText);
  61. console.log("API Call Intercepted:");
  62. console.log(responseText);
  63. var mediaSource = null;
  64. if (responseText.medias) {
  65. mediaSource = responseText.medias[0].source;
  66. } else if (responseText.source) {
  67. mediaSource = responseText.source;
  68. }
  69. window.addEventListener("load", speedhack(mediaSource));
  70. }
  71. function modifyAPICall(responseText) { // Modify the API call to allow skipping.
  72. // 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
  73. return responseText.replace(`"allowSkipAhead":false`, `"allowSkipAhead":true`);
  74. }
  75. function getAPIPath() { // Get the API path
  76. var urlPath = window.location.pathname;
  77. var apiPath;
  78. if (urlPath.startsWith("/assignments/")) {
  79. var id = urlPath.replace("/assignments/", "")
  80. .replace("/watch", "");
  81. apiPath = "/api/v3/assignments/" + id;
  82. } else if (urlPath.startsWith("/media/")) {
  83. var id = urlPath.replace("/media/", "");
  84. apiPath = "/api/v3/media/" + id;
  85. }
  86. return apiPath;
  87. }
  88. 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)
  89. (function(window) {
  90. var OriginalXHR = XMLHttpRequest;
  91. var XHRProxy = function() {
  92. this.xhr = new OriginalXHR();
  93. function delegate(prop) {
  94. Object.defineProperty(this, prop, {
  95. get: function() {
  96. return this.xhr[prop];
  97. },
  98. set: function(value) {
  99. this.xhr.timeout = value;
  100. }
  101. });
  102. }
  103. delegate.call(this, "timeout");
  104. delegate.call(this, "responseType");
  105. delegate.call(this, "withCredentials");
  106. delegate.call(this, "onerror");
  107. delegate.call(this, "onabort");
  108. delegate.call(this, "onloadstart");
  109. delegate.call(this, "onloadend");
  110. delegate.call(this, "onprogress");
  111. };
  112. XHRProxy.prototype.open = function(method, url, async, username, password) {
  113. var ctx = this;
  114. function applyInterceptors(src) {
  115. ctx.responseText = ctx.xhr.responseText;
  116. for (var i = 0; i < XHRProxy.interceptors.length; i++) {
  117. var applied = XHRProxy.interceptors[i](method, url, ctx.responseText, ctx.xhr.status);
  118. if (applied !== undefined) {
  119. ctx.responseText = applied;
  120. }
  121. }
  122. }
  123. function setProps() {
  124. ctx.readyState = ctx.xhr.readyState;
  125. ctx.responseText = ctx.xhr.responseText;
  126. ctx.responseURL = ctx.xhr.responseURL;
  127. ctx.responseXML = ctx.xhr.responseXML;
  128. ctx.status = ctx.xhr.status;
  129. ctx.statusText = ctx.xhr.statusText;
  130. }
  131. this.xhr.open(method, url, async, username, password);
  132. this.xhr.onload = function(evt) {
  133. if (ctx.onload) {
  134. setProps();
  135. if (ctx.xhr.readyState === 4) {
  136. applyInterceptors();
  137. }
  138. return ctx.onload(evt);
  139. }
  140. };
  141. this.xhr.onreadystatechange = function (evt) {
  142. if (ctx.onreadystatechange) {
  143. setProps();
  144. if (ctx.xhr.readyState === 4) {
  145. applyInterceptors();
  146. }
  147. return ctx.onreadystatechange(evt);
  148. }
  149. };
  150. };
  151. XHRProxy.prototype.addEventListener = function(event, fn) {
  152. return this.xhr.addEventListener(event, fn);
  153. };
  154. XHRProxy.prototype.send = function(data) {
  155. return this.xhr.send(data);
  156. };
  157. XHRProxy.prototype.abort = function() {
  158. return this.xhr.abort();
  159. };
  160. XHRProxy.prototype.getAllResponseHeaders = function() {
  161. return this.xhr.getAllResponseHeaders();
  162. };
  163. XHRProxy.prototype.getResponseHeader = function(header) {
  164. return this.xhr.getResponseHeader(header);
  165. };
  166. XHRProxy.prototype.setRequestHeader = function(header, value) {
  167. return this.xhr.setRequestHeader(header, value);
  168. };
  169. XHRProxy.prototype.overrideMimeType = function(mimetype) {
  170. return this.xhr.overrideMimeType(mimetype);
  171. };
  172. XHRProxy.interceptors = [];
  173. XHRProxy.addInterceptor = function(fn) {
  174. this.interceptors.push(fn);
  175. };
  176. window.XMLHttpRequest = XHRProxy;
  177. XHRProxy.addInterceptor(function(method, url, responseText, status) {
  178. if (url === getAPIPath()) {
  179. interceptAPICall(responseText);
  180. return modifyAPICall(responseText);
  181. }
  182. });
  183. })(window);
  184. }
  185. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址