Simple YouTube Age Restriction Bypass

View age restricted videos on YouTube without verification and login :)

目前為 2021-03-31 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Simple YouTube Age Restriction Bypass
  3. // @name:de Umgehe YouTube's Altersbeschränkung und Verifikation
  4. // @namespace https://zerody.one
  5. // @version 0.9
  6. // @description View age restricted videos on YouTube without verification and login :)
  7. // @description:de Schaue YouTube Videos mit Altersbeschränkungen ohne Anmeldung und ohne dein Alter zu bestätigen :)
  8. // @author ZerodyOne (https://github.com/zerodytrash)
  9. // @match https://www.youtube.com/*
  10. // @grant none
  11. // @run-at document-start
  12. // ==/UserScript==
  13.  
  14. (function() {
  15.  
  16. var nativeParse = window.JSON.parse; // Backup the original parse function
  17. var nativeDefineProperty = getNativeDefineProperty(); // Backup the original defineProperty function to intercept setter & getter on the ytInitialPlayerResponse
  18. var wrappedPlayerResponse = null;
  19. var unlockablePlayerStates = ["AGE_VERIFICATION_REQUIRED", "LOGIN_REQUIRED"];
  20. var responseCache = {};
  21.  
  22. // Just for compatibility: Backup original getter/setter for 'ytInitialPlayerResponse', defined by other extensions like AdBlock
  23. var initialPlayerResponseDescriptor = window.Object.getOwnPropertyDescriptor(window, "ytInitialPlayerResponse");
  24. var chainedSetter = initialPlayerResponseDescriptor ? initialPlayerResponseDescriptor.set : null;
  25. var chainedGetter = initialPlayerResponseDescriptor ? initialPlayerResponseDescriptor.get : null;
  26.  
  27. // Just for compatibility: Intercept property (re-)definitions on 'ytInitialPlayerResponse' to chain setter/getter from other extensions by hijacking the Object.defineProperty function
  28. window.Object.defineProperty = function(obj, prop, descriptor) {
  29. if(obj === window && prop === "ytInitialPlayerResponse") {
  30. console.info("Another extension tries to re-define 'ytInitialPlayerResponse' (probably an AdBlock extension). Chain it...");
  31.  
  32. if(descriptor && descriptor.set) chainedSetter = descriptor.set;
  33. if(descriptor && descriptor.get) chainedGetter = descriptor.get;
  34. } else {
  35. nativeDefineProperty(obj, prop, descriptor);
  36. }
  37. }
  38.  
  39. // Re-define 'ytInitialPlayerResponse' to inspect and modify the initial player response as soon as the variable is set on page load
  40. nativeDefineProperty(window, "ytInitialPlayerResponse", {
  41. set: function(playerResponse) {
  42. wrappedPlayerResponse = inspectJsonData(playerResponse);
  43. if(typeof chainedSetter === "function") chainedSetter(wrappedPlayerResponse);
  44. },
  45. get: function() {
  46. if(typeof chainedGetter === "function") try { return chainedGetter() } catch(err) { };
  47. return wrappedPlayerResponse || {};
  48. },
  49. configurable: true
  50. });
  51.  
  52. // Intercept, inspect and modify JSON-based communication to unlock player responses by hijacking the JSON.parse function
  53. window.JSON.parse = function(text, reviver) {
  54. return inspectJsonData(nativeParse(text, reviver));
  55. }
  56.  
  57. function inspectJsonData(parsedData) {
  58. try {
  59. // Unlock #1: Array based in "&pbj=1" AJAX response on any navigation
  60. if(Array.isArray(parsedData)) {
  61. var playerResponseArrayItem = parsedData.find(e => typeof e.playerResponse === "object");
  62. var playerResponse = playerResponseArrayItem ? playerResponseArrayItem.playerResponse : null;
  63.  
  64. if(playerResponse && isUnlockable(playerResponse.playabilityStatus)) {
  65. playerResponseArrayItem.playerResponse = unlockPlayerResponse(playerResponse);
  66. }
  67. }
  68.  
  69. // Unlock #2: Another JSON-Object containing the 'playerResponse'
  70. if(parsedData.playerResponse && parsedData.playerResponse.playabilityStatus && parsedData.playerResponse.videoDetails && isUnlockable(parsedData.playerResponse.playabilityStatus)) {
  71. parsedData.playerResponse = unlockPlayerResponse(parsedData.playerResponse);
  72. }
  73.  
  74. // Unlock #3: Initial page data structure and raw player response
  75. if(parsedData.playabilityStatus && parsedData.videoDetails && isUnlockable(parsedData.playabilityStatus)) {
  76. parsedData = unlockPlayerResponse(parsedData);
  77. }
  78.  
  79. } catch(err) {
  80. console.error("Simple-YouTube-Age-Restriction-Bypass-Error:", err);
  81. }
  82.  
  83. return parsedData;
  84. }
  85.  
  86. function isUnlockable(playabilityStatus) {
  87. if(!playabilityStatus || !playabilityStatus.status) return false;
  88. return unlockablePlayerStates.includes(playabilityStatus.status);
  89. }
  90.  
  91. function unlockPlayerResponse(playerResponse) {
  92. var videoId = playerResponse.videoDetails.videoId;
  93. var unlockedPayerResponse = getUnlockedPlayerResponse(videoId);
  94.  
  95. // check if the unlocked response isn't playable
  96. if(unlockedPayerResponse.playabilityStatus.status !== "OK")
  97. throw ("Simple-YouTube-Age-Restriction-Bypass: Unlock Failed, playabilityStatus: " + unlockedPayerResponse.playabilityStatus.status);
  98.  
  99. return unlockedPayerResponse;
  100. }
  101.  
  102. function getUnlockedPlayerResponse(videoId) {
  103.  
  104. // Check if is cached
  105. if(responseCache.videoId === videoId) return responseCache.content;
  106.  
  107. // Query YT's unrestricted api endpoint
  108. var xmlhttp = new XMLHttpRequest();
  109. xmlhttp.open("GET", "/get_video_info?video_id=" + encodeURIComponent(videoId), false); // Synchronous!!!
  110. xmlhttp.send(null);
  111. var playerResponse = nativeParse(new URLSearchParams(xmlhttp.responseText).get("player_response"));
  112.  
  113. // Fix for https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues/4
  114. if (playerResponse.playabilityStatus.status !== "OK") {
  115. xmlhttp = new XMLHttpRequest();
  116. xmlhttp.open("GET", "/get_video_info?video_id=" + encodeURIComponent(videoId) + "&eurl=https%3A%2F%2Fyoutube.googleapis.com%2Fv%2F" + encodeURIComponent(videoId), false); // Synchronous!!!
  117. xmlhttp.send(null);
  118. playerResponse = nativeParse(new URLSearchParams(xmlhttp.responseText).get("player_response"));
  119. }
  120.  
  121. // If the video is age restricted and the uploader has disallowed the 'Allow embedding' option, these extra params can help in some cases...
  122. if (playerResponse.playabilityStatus.status !== "OK") {
  123. xmlhttp = new XMLHttpRequest();
  124. xmlhttp.open("GET", "/get_video_info?video_id=" + encodeURIComponent(videoId) + "&html5=1&eurl&ps=desktop-polymer&el=adunit&cbr=Chrome&cplatform=DESKTOP&break_type=1&autoplay=1&content_v&authuser=0", false); // Synchronous!!!
  125. xmlhttp.send(null);
  126. playerResponse = nativeParse(new URLSearchParams(xmlhttp.responseText).get("player_response"));
  127. }
  128.  
  129. // Cache response for 10 seconds
  130. responseCache = { videoId: videoId, content: playerResponse };
  131. setTimeout(function() { responseCache = {} }, 10000);
  132.  
  133. return playerResponse;
  134. }
  135.  
  136. // Some extensions like AdBlock override the Object.defineProperty function to prevent a re-definition of the 'ytInitialPlayerResponse' variable by YouTube.
  137. // But we need to define a custom descriptor to that variable to intercept his value. This behavior causes a race condition depending on the execution order with this script :(
  138. // This function tries to restore the native Object.defineProperty function...
  139. function getNativeDefineProperty() {
  140.  
  141. // Check if the Object.defineProperty function is native (original)
  142. if(window.Object.defineProperty && window.Object.defineProperty.toString().indexOf("[native code]") > -1) {
  143. return window.Object.defineProperty;
  144. }
  145.  
  146. // if the Object.defineProperty function is already overidden, try to restore the native function from another window...
  147. try {
  148. if(!document.body) document.body = document.createElement("body");
  149.  
  150. var tempFrame = document.createElement("iframe");
  151. tempFrame.style.display = "none";
  152.  
  153. document.body.insertAdjacentElement("beforeend", tempFrame);
  154. var nativeDefineProperty = tempFrame.contentWindow.Object.defineProperty;
  155. tempFrame.remove();
  156.  
  157. console.info("Simple-YouTube-Age-Restriction-Bypass: Overidden Object.defineProperty function successfully restored!");
  158.  
  159. return nativeDefineProperty;
  160. } catch(err) {
  161. console.warn("Simple-YouTube-Age-Restriction-Bypass: Unable to restore the original Object.defineProperty function", err);
  162. return window.Object.defineProperty;
  163. }
  164. }
  165.  
  166. })();