Advertisement
Guest User

Untitled

a guest
Aug 27th, 2021
792
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 27.42 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Simple YouTube Age Restriction Bypass
  3. // @description Watch age restricted videos on YouTube without login and without age verification :)
  4. // @description:de Schaue YouTube Videos mit Altersbeschränkungen ohne Anmeldung und ohne dein Alter zu bestätigen :)
  5. // @description:fr Regardez des vidéos YouTube avec des restrictions d'âge sans vous inscrire et sans confirmer votre âge :)
  6. // @description:it Guarda i video con restrizioni di età su YouTube senza login e senza verifica dell'età :)
  7. // @version 2.1.3
  8. // @author Zerody (https://github.com/zerodytrash)
  9. // @namespace https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/
  10. // @updateURL https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/raw/main/Simple-YouTube-Age-Restriction-Bypass.user.js
  11. // @downloadURL https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/raw/main/Simple-YouTube-Age-Restriction-Bypass.user.js
  12. // @supportURL https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues
  13. // @license MIT
  14. // @match https://www.youtube.com/*
  15. // @match https://m.youtube.com/*
  16. // @grant none
  17. // @run-at document-start
  18. // @compatible chrome Chrome + Tampermonkey or Violentmonkey
  19. // @compatible firefox Firefox + Greasemonkey or Tampermonkey or Violentmonkey
  20. // @compatible opera Opera + Tampermonkey or Violentmonkey
  21. // @compatible edge Edge + Tampermonkey or Violentmonkey
  22. // @compatible safari Safari + Tampermonkey or Violentmonkey
  23. // ==/UserScript==
  24.  
  25. const initUnlocker = function () {
  26.  
  27. const nativeParse = window.JSON.parse; // Backup the original parse function
  28. const nativeDefineProperty = getNativeDefineProperty(); // Backup the original defineProperty function to intercept setter & getter on the ytInitialPlayerResponse
  29. const nativeXmlHttpOpen = XMLHttpRequest.prototype.open;
  30.  
  31. const unlockablePlayerStates = ["AGE_VERIFICATION_REQUIRED", "AGE_CHECK_REQUIRED", "LOGIN_REQUIRED"];
  32. const playerResponsePropertyAliases = ["ytInitialPlayerResponse", "playerResponse"];
  33.  
  34. let wrappedPlayerResponse = null;
  35. let wrappedNextResponse = null;
  36. let lastProxiedGoogleVideoUrlParams = null;
  37. let responseCache = {};
  38.  
  39. // YouTube API config (Innertube).
  40. // The actual values will be determined later from the global ytcfg variable => setInnertubeConfigFromYtcfg()
  41. let innertubeConfig = {
  42. INNERTUBE_API_KEY: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8",
  43. INNERTUBE_CLIENT_NAME: "WEB",
  44. INNERTUBE_CLIENT_VERSION: "2.20210721.00.00",
  45. INNERTUBE_CONTEXT: {},
  46. STS: 18834, // signatureTimestamp (relevant for the cipher functions)
  47. LOGGED_IN: false
  48. };
  49.  
  50. // The following proxies are currently used as fallback if the innertube age-gate bypass doesn't work...
  51. // You can host your own account proxy instance. See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/tree/main/account-proxy
  52. const accountProxyServerHost = "https://youtube-proxy.zerody.one";
  53. const videoProxyServerHost = "https://phx.4everproxy.com";
  54.  
  55. // UI-related stuff (notifications, ...)
  56. const enableUnlockNotification = true;
  57. let playerCreationObserver = null;
  58. let notificationElement = null;
  59. let notificationTimeout = null;
  60.  
  61. // Just for compatibility: Backup original getter/setter for 'ytInitialPlayerResponse', defined by other extensions like AdBlock
  62. let initialPlayerResponseDescriptor = window.Object.getOwnPropertyDescriptor(window, "ytInitialPlayerResponse");
  63. let chainedPlayerSetter = initialPlayerResponseDescriptor ? initialPlayerResponseDescriptor.set : null;
  64. let chainedPlayerGetter = initialPlayerResponseDescriptor ? initialPlayerResponseDescriptor.get : null;
  65.  
  66. // Just for compatibility: Intercept (re-)definitions on YouTube's initial player response property to chain setter/getter from other extensions by hijacking the Object.defineProperty function
  67. window.Object.defineProperty = function (obj, prop, descriptor) {
  68. if (obj === window && playerResponsePropertyAliases.includes(prop)) {
  69. console.info("Another extension tries to redefine '" + prop + "' (probably an AdBlock extension). Chain it...");
  70.  
  71. if (descriptor && descriptor.set) chainedPlayerSetter = descriptor.set;
  72. if (descriptor && descriptor.get) chainedPlayerGetter = descriptor.get;
  73. } else {
  74. nativeDefineProperty(obj, prop, descriptor);
  75. }
  76. }
  77.  
  78. // Redefine 'ytInitialPlayerResponse' to inspect and modify the initial player response as soon as the variable is set on page load
  79. nativeDefineProperty(window, "ytInitialPlayerResponse", {
  80. set: function (playerResponse) {
  81. // prevent recursive setter calls by ignoring unchanged data (this fixes a problem caused by Brave browser shield)
  82. if (playerResponse === wrappedPlayerResponse) return;
  83.  
  84. wrappedPlayerResponse = inspectJsonData(playerResponse);
  85. if (typeof chainedPlayerSetter === "function") chainedPlayerSetter(wrappedPlayerResponse);
  86. },
  87. get: function () {
  88. if (typeof chainedPlayerGetter === "function") try { return chainedPlayerGetter() } catch (err) { };
  89. return wrappedPlayerResponse || {};
  90. },
  91. configurable: true
  92. });
  93.  
  94. // Also redefine 'ytInitialData' for the initial next/sidebar response
  95. nativeDefineProperty(window, "ytInitialData", {
  96. set: function (nextResponse) {
  97. wrappedNextResponse = inspectJsonData(nextResponse);
  98. },
  99. get: function () {
  100. return wrappedNextResponse;
  101. },
  102. configurable: true
  103. });
  104.  
  105. // Intercept XMLHttpRequest.open to rewrite video URL's (sometimes required)
  106. XMLHttpRequest.prototype.open = function () {
  107. if (arguments.length > 1 && typeof arguments[1] === "string" && arguments[1].indexOf("https://") === 0) {
  108. let method = arguments[0];
  109. let url = new URL(arguments[1]);
  110. let urlParams = new URLSearchParams(url.search);
  111.  
  112. // If the account proxy was used to retieve the video info, the following applies:
  113. // some video files (mostly music videos) can only be accessed from IPs in the same country as the innertube api request (/youtubei/v1/player) was made.
  114. // to get around this, the googlevideo URL will be replaced with a web-proxy URL in the same country (US).
  115. // this is only required if the "gcr=[countrycode]" flag is set in the googlevideo-url...
  116.  
  117. function isGoogleVideo() {
  118. return method === "GET" && url.host.indexOf(".googlevideo.com") > 0;
  119. }
  120.  
  121. function hasGcrFlag() {
  122. return urlParams.get("gcr") !== null;
  123. }
  124.  
  125. function isUnlockedByAccountProxy() {
  126. return urlParams.get("id") !== null && lastProxiedGoogleVideoUrlParams && urlParams.get("id") === lastProxiedGoogleVideoUrlParams.get("id");
  127. }
  128.  
  129. if (videoProxyServerHost && isGoogleVideo() && hasGcrFlag() && isUnlockedByAccountProxy()) {
  130. // rewrite request URL
  131. arguments[1] = videoProxyServerHost + "/direct/" + btoa(arguments[1]);
  132.  
  133. // solve CORS errors by preventing YouTube from enabling the "withCredentials" option (not required for the proxy)
  134. nativeDefineProperty(this, "withCredentials", {
  135. set: function () { },
  136. get: function () { return false; }
  137. });
  138. }
  139.  
  140. }
  141.  
  142. return nativeXmlHttpOpen.apply(this, arguments);
  143. }
  144.  
  145. // Intercept, inspect and modify JSON-based communication to unlock player responses by hijacking the JSON.parse function
  146. window.JSON.parse = function (text, reviver) {
  147. return inspectJsonData(nativeParse(text, reviver));
  148. }
  149.  
  150. function inspectJsonData(parsedData) {
  151.  
  152. // If YouTube does JSON.parse(null) or similar weird things
  153. if (typeof parsedData !== "object" || parsedData === null) return parsedData;
  154.  
  155. try {
  156. // Unlock #1: Array based in "&pbj=1" AJAX response on any navigation (does not seem to be used anymore)
  157. if (Array.isArray(parsedData)) {
  158. let playerResponseArrayItem = parsedData.find(e => typeof e.playerResponse === "object");
  159. let playerResponse = playerResponseArrayItem?.playerResponse;
  160.  
  161. if (playerResponse && isAgeRestricted(playerResponse.playabilityStatus)) {
  162. playerResponseArrayItem.playerResponse = unlockPlayerResponse(playerResponse);
  163.  
  164. let nextResponseArrayItem = parsedData.find(e => typeof e.response === "object");
  165. let nextResponse = nextResponseArrayItem?.response;
  166.  
  167. if (isWatchNextObject(nextResponse) && !isLoggedIn() && isWatchNextSidebarEmpty(nextResponse.contents)) {
  168. nextResponseArrayItem.response = unlockNextResponse(nextResponse);
  169. }
  170. }
  171. }
  172.  
  173. // Hide unlock notification on navigation (if still visible from the last unlock)
  174. if (parsedData.playerResponse || parsedData.playabilityStatus) hidePlayerNotification();
  175.  
  176. // Unlock #2: Another JSON-Object containing the 'playerResponse' (seems to be used by m.youtube.com with &pbj=1)
  177. if (parsedData.playerResponse?.playabilityStatus && parsedData.playerResponse?.videoDetails && isAgeRestricted(parsedData.playerResponse.playabilityStatus)) {
  178. parsedData.playerResponse = unlockPlayerResponse(parsedData.playerResponse);
  179. }
  180.  
  181. // Unlock #3: Initial page data structure and response from the '/youtubei/v1/player' endpoint
  182. if (parsedData.playabilityStatus && parsedData.videoDetails && isAgeRestricted(parsedData.playabilityStatus)) {
  183. parsedData = unlockPlayerResponse(parsedData);
  184. }
  185.  
  186. // Equivelant of unlock #2 for sidebar/next response
  187. if (isWatchNextObject(parsedData.response) && !isLoggedIn() && isWatchNextSidebarEmpty(parsedData.response.contents)) {
  188. parsedData.response = unlockNextResponse(parsedData.response);
  189. }
  190.  
  191. // Equivelant of unlock #3 for sidebar/next response
  192. if (isWatchNextObject(parsedData) && !isLoggedIn() && isWatchNextSidebarEmpty(parsedData.contents)) {
  193. parsedData = unlockNextResponse(parsedData)
  194. }
  195. } catch (err) {
  196. console.error("Simple-YouTube-Age-Restriction-Bypass-Error:", err, "You can report bugs at: https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues");
  197. }
  198.  
  199. return parsedData;
  200. }
  201.  
  202. function isAgeRestricted(playabilityStatus) {
  203. if (!playabilityStatus || !playabilityStatus.status) return false;
  204.  
  205. return typeof playabilityStatus.desktopLegacyAgeGateReason !== "undefined" || unlockablePlayerStates.includes(playabilityStatus.status);
  206. }
  207.  
  208. function isWatchNextObject(parsedData) {
  209. if (!parsedData?.contents) return false;
  210. if (!parsedData?.currentVideoEndpoint?.watchEndpoint?.videoId) return false;
  211.  
  212. return parsedData.contents.twoColumnWatchNextResults || parsedData.contents.singleColumnWatchNextResults;
  213. }
  214.  
  215. function isWatchNextSidebarEmpty(contents) {
  216. let secondaryResults = contents.twoColumnWatchNextResults?.secondaryResults?.secondaryResults;
  217. if (secondaryResults && secondaryResults.results) return false;
  218.  
  219. // MWEB response layout
  220. let singleColumnWatchNextContents = contents.singleColumnWatchNextResults?.results?.results?.contents;
  221. if (!singleColumnWatchNextContents) return true;
  222.  
  223. let itemSectionRendererArrayItem = singleColumnWatchNextContents.find(e => e.itemSectionRenderer?.targetId === "watch-next-feed");
  224. let itemSectionRenderer = itemSectionRendererArrayItem?.itemSectionRenderer;
  225.  
  226. return typeof itemSectionRenderer === "undefined";
  227. }
  228.  
  229. function isLoggedIn() {
  230. setInnertubeConfigFromYtcfg();
  231. return innertubeConfig.LOGGED_IN;
  232. }
  233.  
  234. function unlockPlayerResponse(playerResponse) {
  235. let videoId = playerResponse.videoDetails.videoId;
  236. let reason = playerResponse.playabilityStatus?.status;
  237. let unlockedPayerResponse = getUnlockedPlayerResponse(videoId, reason);
  238.  
  239. // account proxy error?
  240. if (unlockedPayerResponse.errorMessage) {
  241. showPlayerNotification("#7b1e1e", "Unable to unlock this video :( Please look into the developer console for more details. (ProxyError)", 10);
  242. throw new Error(`Unlock Failed, errorMessage:${unlockedPayerResponse.errorMessage}; innertubeApiKey:${innertubeConfig.INNERTUBE_API_KEY}; innertubeClientName:${innertubeConfig.INNERTUBE_CLIENT_NAME}; innertubeClientVersion:${innertubeConfig.INNERTUBE_CLIENT_VERSION}`);
  243. }
  244.  
  245. // check if the unlocked response isn't playable
  246. if (unlockedPayerResponse.playabilityStatus?.status !== "OK") {
  247. showPlayerNotification("#7b1e1e", `Unable to unlock this video :( Please look into the developer console for more details. (playabilityStatus: ${unlockedPayerResponse.playabilityStatus?.status})`, 10);
  248. throw new Error(`Unlock Failed, playabilityStatus:${unlockedPayerResponse.playabilityStatus?.status}; innertubeApiKey:${innertubeConfig.INNERTUBE_API_KEY}; innertubeClientName:${innertubeConfig.INNERTUBE_CLIENT_NAME}; innertubeClientVersion:${innertubeConfig.INNERTUBE_CLIENT_VERSION}`);
  249. }
  250.  
  251. // if the video info was retrieved via proxy, store the URL params from the url- or signatureCipher-attribute to detect later if the requested video files are from this unlock.
  252. // => see isUnlockedByAccountProxy()
  253. if (unlockedPayerResponse.proxied && unlockedPayerResponse.streamingData?.adaptiveFormats) {
  254. let videoUrl = unlockedPayerResponse.streamingData.adaptiveFormats.find(x => x.url)?.url;
  255. let cipherText = unlockedPayerResponse.streamingData.adaptiveFormats.find(x => x.signatureCipher)?.signatureCipher;
  256.  
  257. if (cipherText) videoUrl = new URLSearchParams(cipherText).get("url");
  258.  
  259. lastProxiedGoogleVideoUrlParams = videoUrl ? new URLSearchParams(new URL(videoUrl).search) : null;
  260. }
  261.  
  262. showPlayerNotification("#005c04", "Age-restricted video successfully unlocked!", 4);
  263.  
  264. return unlockedPayerResponse;
  265. }
  266.  
  267. function unlockNextResponse(nextResponse) {
  268. let watchEndpoint = nextResponse.currentVideoEndpoint.watchEndpoint;
  269. let videoId = watchEndpoint.videoId;
  270. let playlistId = watchEndpoint.playlistId;
  271. let playlistIndex = watchEndpoint.index;
  272. let unlockedNextResponse = getUnlockedNextResponse(videoId, playlistId, playlistIndex);
  273.  
  274. // check if the unlocked response's sidebar is still empty
  275. if (isWatchNextSidebarEmpty(unlockedNextResponse.contents)) {
  276. throw new Error(`Sidebar Unlock Failed, innertubeApiKey:${innertubeConfig.INNERTUBE_API_KEY}; innertubeClientName:${innertubeConfig.INNERTUBE_CLIENT_NAME}; innertubeClientVersion:${innertubeConfig.INNERTUBE_CLIENT_VERSION}`);
  277. }
  278.  
  279. // Transfer WatchNextResults to original response
  280. if (nextResponse.contents?.twoColumnWatchNextResults?.secondaryResults) {
  281. nextResponse.contents.twoColumnWatchNextResults.secondaryResults = unlockedNextResponse?.contents?.twoColumnWatchNextResults?.secondaryResults;
  282. }
  283.  
  284. // Transfer mobile (MWEB) WatchNextResults to original response
  285. if (nextResponse.contents?.singleColumnWatchNextResults?.results?.results?.contents) {
  286. let unlockedWatchNextFeed = unlockedNextResponse?.contents?.singleColumnWatchNextResults?.results?.results?.contents?.find(x => x.itemSectionRenderer?.targetId === "watch-next-feed");
  287. if (unlockedWatchNextFeed) nextResponse.contents.singleColumnWatchNextResults.results.results.contents.push(unlockedWatchNextFeed);
  288. }
  289.  
  290. // Transfer video description to original response
  291. let originalVideoSecondaryInfoRenderer = nextResponse.contents?.twoColumnWatchNextResults?.results?.results?.contents?.find(x => x.videoSecondaryInfoRenderer)?.videoSecondaryInfoRenderer;
  292. let unlockedVideoSecondaryInfoRenderer = unlockedNextResponse.contents?.twoColumnWatchNextResults?.results?.results?.contents?.find(x => x.videoSecondaryInfoRenderer)?.videoSecondaryInfoRenderer;
  293.  
  294. if (originalVideoSecondaryInfoRenderer && unlockedVideoSecondaryInfoRenderer?.description) originalVideoSecondaryInfoRenderer.description = unlockedVideoSecondaryInfoRenderer.description;
  295.  
  296. // Transfer mobile (MWEB) video description to original response
  297. let originalStructuredDescriptionContentRenderer = nextResponse.engagementPanels?.find(x => x.engagementPanelSectionListRenderer)?.engagementPanelSectionListRenderer?.content?.structuredDescriptionContentRenderer?.items?.find(x => x.expandableVideoDescriptionBodyRenderer);
  298. let unlockedStructuredDescriptionContentRenderer = unlockedNextResponse.engagementPanels?.find(x => x.engagementPanelSectionListRenderer)?.engagementPanelSectionListRenderer?.content?.structuredDescriptionContentRenderer?.items?.find(x => x.expandableVideoDescriptionBodyRenderer);
  299.  
  300. if (originalStructuredDescriptionContentRenderer && unlockedStructuredDescriptionContentRenderer?.expandableVideoDescriptionBodyRenderer) originalStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer = unlockedStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer;
  301.  
  302. return nextResponse;
  303. }
  304.  
  305. function getUnlockedPlayerResponse(videoId, reason) {
  306. // Check if response is cached
  307. if (responseCache.videoId === videoId) return responseCache.content;
  308.  
  309. setInnertubeConfigFromYtcfg();
  310.  
  311. let playerResponse = null;
  312.  
  313. // Strategy 1: Retrieve the video info by using a age-gate bypass for the innertube API
  314. // Source: https://github.com/yt-dlp/yt-dlp/issues/574#issuecomment-887171136
  315. function useInnertubeEmbed() {
  316. console.info("Simple-YouTube-Age-Restriction-Bypass: Trying Unlock Method #1 (Innertube Embed)");
  317. const payload = getInnertubeEmbedPayload(videoId);
  318. const xmlhttp = new XMLHttpRequest();
  319. xmlhttp.open("POST", `/youtubei/v1/player?key=${innertubeConfig.INNERTUBE_API_KEY}`, false); // Synchronous!!!
  320. xmlhttp.send(JSON.stringify(payload));
  321. playerResponse = nativeParse(xmlhttp.responseText);
  322. }
  323.  
  324. // Strategy 2: Retrieve the video info from an account proxy server.
  325. // See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/tree/main/account-proxy
  326. function useProxy() {
  327. console.info("Simple-YouTube-Age-Restriction-Bypass: Trying Unlock Method #2 (Account Proxy)");
  328. const xmlhttp = new XMLHttpRequest();
  329. xmlhttp.open("GET", accountProxyServerHost + `/getPlayer?videoId=${encodeURIComponent(videoId)}&reason=${encodeURIComponent(reason)}&clientName=${innertubeConfig.INNERTUBE_CLIENT_NAME}&clientVersion=${innertubeConfig.INNERTUBE_CLIENT_VERSION}&signatureTimestamp=${innertubeConfig.STS}`, false); // Synchronous!!!
  330. xmlhttp.send(null);
  331. playerResponse = nativeParse(xmlhttp.responseText);
  332. playerResponse.proxied = true;
  333. }
  334.  
  335. if (playerResponse?.playabilityStatus?.status !== "OK") useInnertubeEmbed();
  336. if (playerResponse?.playabilityStatus?.status !== "OK") useProxy();
  337.  
  338. // Cache response for 10 seconds
  339. responseCache = { videoId: videoId, content: playerResponse };
  340. setTimeout(function () { responseCache = {} }, 10000);
  341.  
  342. return playerResponse;
  343. }
  344.  
  345. function getUnlockedNextResponse(videoId, playlistId, playlistIndex) {
  346.  
  347. setInnertubeConfigFromYtcfg();
  348.  
  349. // Retrieve the video info by using a age-gate bypass for the innertube API
  350. // Source: https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues/16#issuecomment-889232425
  351. console.info("Simple-YouTube-Age-Restriction-Bypass: Trying Sidebar Unlock Method (Innertube Embed)");
  352. const payload = getInnertubeEmbedPayload(videoId, playlistId, playlistIndex);
  353. const xmlhttp = new XMLHttpRequest();
  354. xmlhttp.open("POST", `/youtubei/v1/next?key=${innertubeConfig.INNERTUBE_API_KEY}`, false); // Synchronous!!!
  355. xmlhttp.send(JSON.stringify(payload));
  356. return nativeParse(xmlhttp.responseText);
  357. }
  358.  
  359. function getInnertubeEmbedPayload(videoId, playlistId, playlistIndex) {
  360. let data = {
  361. context: {
  362. client: {
  363. clientName: innertubeConfig.INNERTUBE_CLIENT_NAME.replace('_EMBEDDED_PLAYER', ''),
  364. clientVersion: innertubeConfig.INNERTUBE_CLIENT_VERSION,
  365. clientScreen: "EMBED"
  366. },
  367. thirdParty: {
  368. embedUrl: "https://www.youtube.com/"
  369. }
  370. },
  371. playbackContext: {
  372. contentPlaybackContext: {
  373. signatureTimestamp: innertubeConfig.STS
  374. }
  375. },
  376. videoId: videoId,
  377. playlistId: playlistId,
  378. playlistIndex: playlistIndex
  379. }
  380.  
  381. // Append client info from INNERTUBE_CONTEXT
  382. if (typeof innertubeConfig.INNERTUBE_CONTEXT?.client === "object") {
  383. data.context.client = Object.assign(innertubeConfig.INNERTUBE_CONTEXT.client, data.context.client);
  384. }
  385.  
  386. return data;
  387. }
  388.  
  389. // to avoid version conflicts between client and server response, the current YouTube version config will be determined
  390. function setInnertubeConfigFromYtcfg() {
  391. if (!window.ytcfg) {
  392. console.warn("Simple-YouTube-Age-Restriction-Bypass: Unable to retrieve global YouTube configuration (window.ytcfg). Using old values...");
  393. return;
  394. }
  395.  
  396. for (const key in innertubeConfig) {
  397. const value = window.ytcfg.data_?.[key] ?? window.ytcfg.get?.(key);
  398. if (typeof value !== "undefined" && value !== null) {
  399. innertubeConfig[key] = value;
  400. } else {
  401. console.warn(`Simple-YouTube-Age-Restriction-Bypass: Unable to retrieve global YouTube configuration variable '${key}'. Using old value...`);
  402. }
  403. }
  404. }
  405.  
  406. function showPlayerNotification(color, message, displayDuration) {
  407. if (!enableUnlockNotification) return;
  408. if (typeof MutationObserver !== "function") return;
  409.  
  410. try {
  411. // clear existing notifications
  412. disconnectPlayerCreationObserver();
  413. hidePlayerNotification();
  414.  
  415. function getPlayerElement() {
  416. return document.querySelector("#primary > #primary-inner > #player") || document.querySelector("#player-container-id > #player");
  417. }
  418.  
  419. function createNotification() {
  420. let playerElement = getPlayerElement();
  421. if (!playerElement) return;
  422.  
  423. // first, remove existing notification
  424. hidePlayerNotification();
  425.  
  426. // create new notification
  427. notificationElement = document.createElement("div");
  428. notificationElement.innerHTML = message;
  429. notificationElement.style = `width: 100%; text-align: center; background-color: ${color}; color: #ffffff; padding: 2px 0px 2px; font-size: 1.1em;`;
  430. notificationElement.id = "bypass-notification";
  431.  
  432. // append below the player
  433. playerElement.parentNode.insertBefore(notificationElement, playerElement.nextSibling);
  434.  
  435. if (notificationTimeout) {
  436. clearTimeout(notificationTimeout);
  437. notificationTimeout = null;
  438. }
  439.  
  440. notificationTimeout = setTimeout(hidePlayerNotification, displayDuration * 1000);
  441. }
  442.  
  443. function disconnectPlayerCreationObserver() {
  444. if (playerCreationObserver) {
  445. playerCreationObserver.disconnect();
  446. playerCreationObserver = null;
  447. }
  448. }
  449.  
  450. // Does the player already exist in the DOM?
  451. if (getPlayerElement() !== null) {
  452. createNotification();
  453. return;
  454. }
  455.  
  456. // waiting for creation of the player element...
  457. playerCreationObserver = new MutationObserver(function (mutations) {
  458. if (getPlayerElement() !== null) {
  459. disconnectPlayerCreationObserver();
  460. createNotification();
  461. }
  462. });
  463.  
  464. playerCreationObserver.observe(document.body, { childList: true });
  465. } catch (err) { }
  466. }
  467.  
  468. function hidePlayerNotification() {
  469. if (playerCreationObserver) {
  470. playerCreationObserver.disconnect();
  471. playerCreationObserver = null;
  472. }
  473.  
  474. if (notificationElement) {
  475. notificationElement.remove();
  476. notificationElement = null;
  477. }
  478. }
  479.  
  480. // Some extensions like AdBlock override the Object.defineProperty function to prevent a redefinition of the 'ytInitialPlayerResponse' variable by YouTube.
  481. // 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 :(
  482. // This function tries to restore the native Object.defineProperty function...
  483. function getNativeDefineProperty() {
  484. // Check if the Object.defineProperty function is native (original)
  485. if (window.Object.defineProperty && window.Object.defineProperty.toString().indexOf("[native code]") > -1) {
  486. return window.Object.defineProperty;
  487. }
  488.  
  489. // if the Object.defineProperty function is already overidden, try to restore the native function from another window...
  490. try {
  491. if (!document.body) document.body = document.createElement("body");
  492.  
  493. let tempFrame = document.createElement("iframe");
  494. tempFrame.style.display = "none";
  495.  
  496. document.body.insertAdjacentElement("beforeend", tempFrame);
  497. let nativeDefineProperty = tempFrame.contentWindow.Object.defineProperty;
  498. tempFrame.remove();
  499.  
  500. console.info("Simple-YouTube-Age-Restriction-Bypass: Overidden Object.defineProperty function successfully restored!");
  501.  
  502. return nativeDefineProperty;
  503. } catch (err) {
  504. console.warn("Simple-YouTube-Age-Restriction-Bypass: Unable to restore the original Object.defineProperty function", err);
  505. return window.Object.defineProperty;
  506. }
  507. }
  508.  
  509. };
  510.  
  511. // Just a trick to get around the sandbox restrictions in Firefox / Greasemonkey
  512. // Greasemonkey => Inject code into the main window
  513. // Tampermonkey & Violentmonkey => Execute code directly
  514. if (typeof GM_info === "object" && GM_info.scriptHandler === "Greasemonkey") {
  515. window.eval("(" + initUnlocker.toString() + ")();");
  516. } else {
  517. initUnlocker();
  518. }
  519.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement