YouTube Age Restriction Bypass (Stealth Version)

Safely bypass YouTube age restrictions, undetectably, for testing/educational purposes only.

  1. // ==UserScript==
  2. // @name YouTube Age Restriction Bypass (Stealth Version)
  3. // @namespace http://tampermonkey.net/
  4. // @version 5.0
  5. // @description Safely bypass YouTube age restrictions, undetectably, for testing/educational purposes only.
  6. // @author
  7. // @match *://www.youtube.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. 'use strict';
  13.  
  14. const debug = false;
  15. const log = msg => debug && console.log(`[YTBypass]: ${msg}`);
  16.  
  17. function overridePlayability(data) {
  18. if (!data?.playabilityStatus) return data;
  19. const status = data.playabilityStatus.status;
  20. if (['AGE_VERIFICATION_REQUIRED', 'LOGIN_REQUIRED', 'UNPLAYABLE', 'RESTRICTED'].includes(status)) {
  21. data.playabilityStatus.status = 'OK';
  22. delete data.playabilityStatus.errorScreen;
  23. delete data.playabilityStatus.messages;
  24. log('Patched playability status.');
  25. }
  26. return data;
  27. }
  28.  
  29. // Safe proxy for fetch
  30. const nativeFetch = window.fetch;
  31. window.fetch = new Proxy(nativeFetch, {
  32. apply(target, thisArg, args) {
  33. return Reflect.apply(target, thisArg, args).then(async response => {
  34. const cloned = response.clone();
  35. const url = (typeof args[0] === 'string') ? args[0] : args[0].url || '';
  36.  
  37. if (url.includes('/youtubei/v1/player')) {
  38. try {
  39. const json = await cloned.json();
  40. const modified = overridePlayability(JSON.parse(JSON.stringify(json)));
  41. const blob = new Blob([JSON.stringify(modified)], { type: 'application/json' });
  42. return new Response(blob, {
  43. status: response.status,
  44. statusText: response.statusText,
  45. headers: response.headers
  46. });
  47. } catch (e) {
  48. log(`Fetch patch failed: ${e.message}`);
  49. return response;
  50. }
  51. }
  52. return response;
  53. });
  54. }
  55. });
  56.  
  57. // Safe proxy for XMLHttpRequest
  58. const NativeXHR = XMLHttpRequest;
  59. window.XMLHttpRequest = new Proxy(NativeXHR, {
  60. construct(target, args) {
  61. const xhr = new target(...args);
  62. let isBypassURL = false;
  63. const originalOpen = xhr.open;
  64.  
  65. xhr.open = function (method, url) {
  66. isBypassURL = url.includes('/youtubei/v1/player');
  67. return originalOpen.apply(this, arguments);
  68. };
  69.  
  70. const originalSend = xhr.send;
  71. xhr.send = function () {
  72. if (isBypassURL) {
  73. xhr.addEventListener('readystatechange', function () {
  74. if (xhr.readyState === 4 && xhr.responseType === '' && xhr.responseText) {
  75. try {
  76. const json = JSON.parse(xhr.responseText);
  77. overridePlayability(json);
  78. } catch (e) {
  79. log('XHR patch failed');
  80. }
  81. }
  82. });
  83. }
  84. return originalSend.apply(this, arguments);
  85. };
  86.  
  87. return xhr;
  88. }
  89. });
  90.  
  91. // Inject script in page context to patch initial responses
  92. function injectStealthPatchScript() {
  93. const script = document.createElement('script');
  94. script.textContent = `
  95. (function() {
  96. try {
  97. const patch = (res) => {
  98. if (!res?.playabilityStatus) return;
  99. const status = res.playabilityStatus.status;
  100. if (['AGE_VERIFICATION_REQUIRED','RESTRICTED','LOGIN_REQUIRED'].includes(status)) {
  101. res.playabilityStatus.status = 'OK';
  102. delete res.playabilityStatus.errorScreen;
  103. console.log('[YTBypass] ytInitialPlayerResponse patched.');
  104. }
  105. };
  106. if (window.ytInitialPlayerResponse) patch(window.ytInitialPlayerResponse);
  107. if (window.ytplayer?.config?.args?.player_response) {
  108. const res = JSON.parse(window.ytplayer.config.args.player_response);
  109. patch(res);
  110. window.ytplayer.config.args.player_response = JSON.stringify(res);
  111. }
  112. } catch (_) {}
  113. })();
  114. `;
  115. document.documentElement.appendChild(script);
  116. script.remove();
  117. }
  118.  
  119. // Monitor page changes for player reload
  120. new MutationObserver(() => {
  121. if (document.querySelector('ytd-watch-flexy[is-restricted]')) {
  122. log('Restricted video detected.');
  123. setTimeout(injectStealthPatchScript, 1000);
  124. }
  125. }).observe(document.body, { childList: true, subtree: true });
  126. })();