Advertisement
Guest User

Bypass YouTube Age

a guest
Nov 29th, 2021
1,215
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 33.85 KB | None | 0 0
  1.  
  2. // ==UserScript==
  3. // @name Simple YouTube Age Restriction Bypass
  4. // @description Watch age restricted videos on YouTube without login and without age verification :)
  5. // @description:de Schaue YouTube Videos mit Altersbeschränkungen ohne Anmeldung und ohne dein Alter zu bestätigen :)
  6. // @description:fr Regardez des vidéos YouTube avec des restrictions d'âge sans vous inscrire et sans confirmer votre âge :)
  7. // @description:it Guarda i video con restrizioni di età su YouTube senza login e senza verifica dell'età :)
  8. // @version 2.2.2
  9. // @author Zerody (https://github.com/zerodytrash)
  10. // @namespace https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/
  11. // @supportURL https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues
  12. // @license MIT
  13. // @match https://www.youtube.com/*
  14. // @match https://m.youtube.com/*
  15. // @grant none
  16. // @run-at document-start
  17. // @compatible chrome Chrome + Tampermonkey or Violentmonkey
  18. // @compatible firefox Firefox + Greasemonkey or Tampermonkey or Violentmonkey
  19. // @compatible opera Opera + Tampermonkey or Violentmonkey
  20. // @compatible edge Edge + Tampermonkey or Violentmonkey
  21. // @compatible safari Safari + Tampermonkey or Violentmonkey
  22. // ==/UserScript==
  23.  
  24. /*
  25. This is a transpiled version to achieve a clean code base and better browser compatibility.
  26. You can find the nicely readable source code at https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass
  27. */
  28.  
  29. (function iife(inject) {
  30. // Trick to get around the sandbox restrictions in Greasemonkey (Firefox)
  31. // Inject code into the main window if criteria match
  32. if (typeof GM_info === "object" && GM_info.scriptHandler === "Greasemonkey" && inject) {
  33. window.eval("(" + iife.toString() + ")();");
  34. return;
  35. }
  36.  
  37.  
  38. // Script configuration variables
  39. const UNLOCKABLE_PLAYER_STATES = ["AGE_VERIFICATION_REQUIRED", "AGE_CHECK_REQUIRED", "LOGIN_REQUIRED"];
  40. const PLAYER_RESPONSE_ALIASES = ["ytInitialPlayerResponse", "playerResponse"];
  41.  
  42. // The following proxies are currently used as fallback if the innertube age-gate bypass doesn't work...
  43. // You can host your own account proxy instance. See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/tree/main/account-proxy
  44. const ACCOUNT_PROXY_SERVER_HOST = "https://youtube-proxy.zerody.one";
  45. const VIDEO_PROXY_SERVER_HOST = "https://phx.4everproxy.com";
  46.  
  47. const isDesktop = window.location.host !== "m.youtube.com";
  48. const isEmbed = window.location.pathname.includes("/embed/");
  49.  
  50. class Deferred {
  51. constructor() {
  52. return Object.assign(new Promise((resolve, reject) => {
  53. this.resolve = resolve;
  54. this.reject = reject;
  55. }), this);
  56. }}
  57.  
  58.  
  59. function createElement(tagName, options) {
  60. const node = document.createElement(tagName);
  61. options && Object.assign(node, options);
  62. return node;
  63. }
  64.  
  65. function isObject(obj) {
  66. return obj !== null && typeof obj === "object";
  67. }
  68.  
  69. function getCookie(name) {
  70. const value = `; ${document.cookie}`;
  71. const parts = value.split(`; ${name}=`);
  72. if (parts.length === 2) return parts.pop().split(';').shift();
  73. }
  74.  
  75. // Source: https://coursesweb.net/javascript/sha1-encrypt-data_cs
  76. function generateSha1Hash(msg) {
  77. function rotate_left(n, s) {
  78. var t4 = n << s | n >>> 32 - s;
  79. return t4;
  80. }
  81. function cvt_hex(val) {
  82. var str = '';
  83. var i;
  84. var v;
  85. for (i = 7; i >= 0; i--) {
  86. v = val >>> i * 4 & 0x0f;
  87. str += v.toString(16);
  88. }
  89. return str;
  90. }
  91. function Utf8Encode(string) {
  92. string = string.replace(/\r\n/g, '\n');
  93. var utftext = '';
  94. for (var n = 0; n < string.length; n++) {
  95. var c = string.charCodeAt(n);
  96. if (c < 128) {
  97. utftext += String.fromCharCode(c);
  98. } else
  99. if (c > 127 && c < 2048) {
  100. utftext += String.fromCharCode(c >> 6 | 192);
  101. utftext += String.fromCharCode(c & 63 | 128);
  102. } else
  103. {
  104. utftext += String.fromCharCode(c >> 12 | 224);
  105. utftext += String.fromCharCode(c >> 6 & 63 | 128);
  106. utftext += String.fromCharCode(c & 63 | 128);
  107. }
  108. }
  109. return utftext;
  110. }
  111. var blockstart;
  112. var i, j;
  113. var W = new Array(80);
  114. var H0 = 0x67452301;
  115. var H1 = 0xEFCDAB89;
  116. var H2 = 0x98BADCFE;
  117. var H3 = 0x10325476;
  118. var H4 = 0xC3D2E1F0;
  119. var A, B, C, D, E;
  120. var temp;
  121. msg = Utf8Encode(msg);
  122. var msg_len = msg.length;
  123. var word_array = new Array();
  124. for (i = 0; i < msg_len - 3; i += 4) {
  125. j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 |
  126. msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3);
  127. word_array.push(j);
  128. }
  129. switch (msg_len % 4) {
  130. case 0:
  131. i = 0x080000000;
  132. break;
  133. case 1:
  134. i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000;
  135. break;
  136. case 2:
  137. i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000;
  138. break;
  139. case 3:
  140. i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80;
  141. break;}
  142.  
  143. word_array.push(i);
  144. while (word_array.length % 16 != 14) word_array.push(0);
  145. word_array.push(msg_len >>> 29);
  146. word_array.push(msg_len << 3 & 0x0ffffffff);
  147. for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
  148. for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i];
  149. for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
  150. A = H0;
  151. B = H1;
  152. C = H2;
  153. D = H3;
  154. E = H4;
  155. for (i = 0; i <= 19; i++) {
  156. temp = rotate_left(A, 5) + (B & C | ~B & D) + E + W[i] + 0x5A827999 & 0x0ffffffff;
  157. E = D;
  158. D = C;
  159. C = rotate_left(B, 30);
  160. B = A;
  161. A = temp;
  162. }
  163. for (i = 20; i <= 39; i++) {
  164. temp = rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1 & 0x0ffffffff;
  165. E = D;
  166. D = C;
  167. C = rotate_left(B, 30);
  168. B = A;
  169. A = temp;
  170. }
  171. for (i = 40; i <= 59; i++) {
  172. temp = rotate_left(A, 5) + (B & C | B & D | C & D) + E + W[i] + 0x8F1BBCDC & 0x0ffffffff;
  173. E = D;
  174. D = C;
  175. C = rotate_left(B, 30);
  176. B = A;
  177. A = temp;
  178. }
  179. for (i = 60; i <= 79; i++) {
  180. temp = rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6 & 0x0ffffffff;
  181. E = D;
  182. D = C;
  183. C = rotate_left(B, 30);
  184. B = A;
  185. A = temp;
  186. }
  187. H0 = H0 + A & 0x0ffffffff;
  188. H1 = H1 + B & 0x0ffffffff;
  189. H2 = H2 + C & 0x0ffffffff;
  190. H3 = H3 + D & 0x0ffffffff;
  191. H4 = H4 + E & 0x0ffffffff;
  192. }
  193.  
  194. return (cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4)).toLowerCase();
  195. }
  196.  
  197. const nativeJSONParse = window.JSON.parse;
  198.  
  199. const nativeXMLHttpRequestOpen = XMLHttpRequest.prototype.open;
  200.  
  201. // Some extensions like AdBlock override the Object.defineProperty function to prevent a redefinition of the 'ytInitialPlayerResponse' variable by YouTube.
  202. // But we need to define a custom descriptor to that variable to intercept its value. This behavior causes a race condition depending on the execution order with this script :(
  203. // To solve this problem the native defineProperty function will be retrieved from another window (iframe)
  204. const nativeObjectDefineProperty = (() => {
  205. // Check if function is native
  206. if (Object.defineProperty.toString().includes("[native code]")) {
  207. return Object.defineProperty;
  208. }
  209.  
  210. // If function is overidden, restore the native function from another window...
  211. const tempFrame = createElement("iframe", { style: `display: none;` });
  212. document.documentElement.append(tempFrame);
  213.  
  214. const native = tempFrame.contentWindow.Object.defineProperty;
  215.  
  216. tempFrame.remove();
  217.  
  218. return native;
  219. })();
  220.  
  221. let wrappedPlayerResponse;
  222. let wrappedNextResponse;
  223.  
  224. function attachInitialDataInterceptor(onInititalDataSet) {
  225.  
  226. // Just for compatibility: Backup original getter/setter for 'ytInitialPlayerResponse', defined by other extensions like AdBlock
  227. let { get: chainedPlayerGetter, set: chainedPlayerSetter } = Object.getOwnPropertyDescriptor(window, "ytInitialPlayerResponse") || {};
  228.  
  229. // 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
  230. Object.defineProperty = (obj, prop, descriptor) => {
  231. if (obj === window && PLAYER_RESPONSE_ALIASES.includes(prop)) {
  232. console.info("Another extension tries to redefine '" + prop + "' (probably an AdBlock extension). Chain it...");
  233.  
  234. if (descriptor !== null && descriptor !== void 0 && descriptor.set) chainedPlayerSetter = descriptor.set;
  235. if (descriptor !== null && descriptor !== void 0 && descriptor.get) chainedPlayerGetter = descriptor.get;
  236. } else {
  237. nativeObjectDefineProperty(obj, prop, descriptor);
  238. }
  239. };
  240.  
  241. // Redefine 'ytInitialPlayerResponse' to inspect and modify the initial player response as soon as the variable is set on page load
  242. nativeObjectDefineProperty(window, "ytInitialPlayerResponse", {
  243. set: (playerResponse) => {
  244. // prevent recursive setter calls by ignoring unchanged data (this fixes a problem caused by Brave browser shield)
  245. if (playerResponse === wrappedPlayerResponse) return;
  246.  
  247. wrappedPlayerResponse = isObject(playerResponse) ? onInititalDataSet(playerResponse) : playerResponse;
  248. if (typeof chainedPlayerSetter === "function") chainedPlayerSetter(wrappedPlayerResponse);
  249. },
  250. get: () => {
  251. // eslint-disable-next-line no-empty
  252. if (typeof chainedPlayerGetter === "function") try {return chainedPlayerGetter();} catch (err) {}
  253. return wrappedPlayerResponse || {};
  254. },
  255. configurable: true });
  256.  
  257.  
  258. // Also redefine 'ytInitialData' for the initial next/sidebar response
  259. nativeObjectDefineProperty(window, "ytInitialData", {
  260. set: (nextResponse) => {wrappedNextResponse = isObject(nextResponse) ? onInititalDataSet(nextResponse) : nextResponse;},
  261. get: () => wrappedNextResponse,
  262. configurable: true });
  263.  
  264. }
  265.  
  266. // Intercept, inspect and modify JSON-based communication to unlock player responses by hijacking the JSON.parse function
  267. function attachJsonInterceptor(onJsonDataReceived) {
  268. window.JSON.parse = (text, reviver) => {
  269. const data = nativeJSONParse(text, reviver);
  270. return !isObject(data) ? data : onJsonDataReceived(data);
  271. };
  272. }
  273.  
  274. function attachXhrOpenInterceptor(onXhrOpenCalled) {
  275. XMLHttpRequest.prototype.open = function (method, url) {
  276. if (arguments.length > 1 && typeof url === "string" && url.indexOf("https://") === 0) {
  277. const modifiedUrl = onXhrOpenCalled(this, method, new URL(url));
  278.  
  279. if (typeof modifiedUrl === "string") {
  280. url = modifiedUrl;
  281. }
  282. }
  283.  
  284. nativeXMLHttpRequestOpen.apply(this, arguments);
  285. };
  286. }
  287.  
  288. function isPlayerObject(parsedData) {
  289. return (parsedData === null || parsedData === void 0 ? void 0 : parsedData.videoDetails) && (parsedData === null || parsedData === void 0 ? void 0 : parsedData.playabilityStatus);
  290. }
  291.  
  292. function isEmbeddedPlayerObject(parsedData) {
  293. return typeof (parsedData === null || parsedData === void 0 ? void 0 : parsedData.previewPlayabilityStatus) === "object";
  294. }
  295.  
  296. function isAgeRestricted(playabilityStatus) {var _playabilityStatus$er, _playabilityStatus$er2, _playabilityStatus$er3, _playabilityStatus$er4, _playabilityStatus$er5, _playabilityStatus$er6, _playabilityStatus$er7, _playabilityStatus$er8;
  297. if (!(playabilityStatus !== null && playabilityStatus !== void 0 && playabilityStatus.status)) return false;
  298. if (playabilityStatus.desktopLegacyAgeGateReason) return true;
  299. if (UNLOCKABLE_PLAYER_STATES.includes(playabilityStatus.status)) return true;
  300.  
  301. // Fix to detect age restrictions on embed player
  302. // see https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues/85#issuecomment-946853553
  303. return isEmbed && ((_playabilityStatus$er = playabilityStatus.errorScreen) === null || _playabilityStatus$er === void 0 ? void 0 : (_playabilityStatus$er2 = _playabilityStatus$er.playerErrorMessageRenderer) === null || _playabilityStatus$er2 === void 0 ? void 0 : (_playabilityStatus$er3 = _playabilityStatus$er2.reason) === null || _playabilityStatus$er3 === void 0 ? void 0 : (_playabilityStatus$er4 = _playabilityStatus$er3.runs) === null || _playabilityStatus$er4 === void 0 ? void 0 : (_playabilityStatus$er5 = _playabilityStatus$er4.
  304. find((x) => x.navigationEndpoint)) === null || _playabilityStatus$er5 === void 0 ? void 0 : (_playabilityStatus$er6 = _playabilityStatus$er5.navigationEndpoint) === null || _playabilityStatus$er6 === void 0 ? void 0 : (_playabilityStatus$er7 = _playabilityStatus$er6.urlEndpoint) === null || _playabilityStatus$er7 === void 0 ? void 0 : (_playabilityStatus$er8 = _playabilityStatus$er7.url) === null || _playabilityStatus$er8 === void 0 ? void 0 : _playabilityStatus$er8.includes("/2802167"));
  305. }
  306.  
  307. function isWatchNextObject(parsedData) {var _parsedData$currentVi, _parsedData$currentVi2;
  308. if (!(parsedData !== null && parsedData !== void 0 && parsedData.contents) || !(parsedData !== null && parsedData !== void 0 && (_parsedData$currentVi = parsedData.currentVideoEndpoint) !== null && _parsedData$currentVi !== void 0 && (_parsedData$currentVi2 = _parsedData$currentVi.watchEndpoint) !== null && _parsedData$currentVi2 !== void 0 && _parsedData$currentVi2.videoId)) return false;
  309. return !!parsedData.contents.twoColumnWatchNextResults || !!parsedData.contents.singleColumnWatchNextResults;
  310. }
  311.  
  312. function isWatchNextSidebarEmpty(parsedData) {var _parsedData$contents2, _parsedData$contents3, _parsedData$contents4, _parsedData$contents5, _content$find;
  313. if (isDesktop) {var _parsedData$contents, _parsedData$contents$, _parsedData$contents$2, _parsedData$contents$3;
  314. // WEB response layout
  315. const result = (_parsedData$contents = parsedData.contents) === null || _parsedData$contents === void 0 ? void 0 : (_parsedData$contents$ = _parsedData$contents.twoColumnWatchNextResults) === null || _parsedData$contents$ === void 0 ? void 0 : (_parsedData$contents$2 = _parsedData$contents$.secondaryResults) === null || _parsedData$contents$2 === void 0 ? void 0 : (_parsedData$contents$3 = _parsedData$contents$2.secondaryResults) === null || _parsedData$contents$3 === void 0 ? void 0 : _parsedData$contents$3.results;
  316. return !result;
  317. }
  318.  
  319. // MWEB response layout
  320. const content = (_parsedData$contents2 = parsedData.contents) === null || _parsedData$contents2 === void 0 ? void 0 : (_parsedData$contents3 = _parsedData$contents2.singleColumnWatchNextResults) === null || _parsedData$contents3 === void 0 ? void 0 : (_parsedData$contents4 = _parsedData$contents3.results) === null || _parsedData$contents4 === void 0 ? void 0 : (_parsedData$contents5 = _parsedData$contents4.results) === null || _parsedData$contents5 === void 0 ? void 0 : _parsedData$contents5.contents;
  321. const result = content === null || content === void 0 ? void 0 : (_content$find = content.find((e) => {var _e$itemSectionRendere;return ((_e$itemSectionRendere = e.itemSectionRenderer) === null || _e$itemSectionRendere === void 0 ? void 0 : _e$itemSectionRendere.targetId) === "watch-next-feed";})) === null || _content$find === void 0 ? void 0 : _content$find.itemSectionRenderer;
  322. return typeof result !== "object";
  323. }
  324.  
  325. function isGoogleVideo(method, url) {
  326. return method === "GET" && url.host.includes(".googlevideo.com");
  327. }
  328.  
  329. function isGoogleVideoUnlockRequired(googleVideoUrl, lastProxiedGoogleVideoId) {
  330. const urlParams = new URLSearchParams(googleVideoUrl.search);
  331. const hasGcrFlag = urlParams.get("gcr");
  332. const wasUnlockedByAccountProxy = urlParams.get("id") === lastProxiedGoogleVideoId;
  333.  
  334. return hasGcrFlag && wasUnlockedByAccountProxy;
  335. }
  336.  
  337. function getYtcfgValue(value) {var _window$ytcfg;
  338. return (_window$ytcfg = window.ytcfg) === null || _window$ytcfg === void 0 ? void 0 : _window$ytcfg.get(value);
  339. }
  340.  
  341. function isUserLoggedIn() {
  342. // Session Cookie exists?
  343. if (!getSidCookie()) return false;
  344.  
  345. // LOGGED_IN doesn't exist on embedded page, use DELEGATED_SESSION_ID as fallback
  346. if (typeof getYtcfgValue('LOGGED_IN') === "boolean") return getYtcfgValue('LOGGED_IN');
  347. if (typeof getYtcfgValue('DELEGATED_SESSION_ID') === "string") return true;
  348.  
  349. return false;
  350. }
  351.  
  352. function getPlayer$1(videoId, clientConfig, useAuth) {
  353. const payload = getInnertubeEmbedPayload(videoId, clientConfig);
  354. return sendInnertubeRequest('v1/player', payload, useAuth);
  355. }
  356.  
  357. function getNext(videoId, clientConfig, playlistId, playlistIndex) {
  358. const payload = getInnertubeEmbedPayload(videoId, clientConfig, playlistId, playlistIndex);
  359. return sendInnertubeRequest('v1/next', payload, false);
  360. }
  361.  
  362. function getMainPageClientName() {
  363. // replace embedded client with YouTube's main page client (e.g. WEB_EMBEDDED_PLAYER => WEB)
  364. return getYtcfgValue('INNERTUBE_CLIENT_NAME').replace('_EMBEDDED_PLAYER', '');
  365. }
  366.  
  367. function getSignatureTimestamp() {
  368. return getYtcfgValue('STS') || (() => {var _document$querySelect;
  369. // STS is missing on embedded player. Retrieve from player base script as fallback...
  370. const playerBaseJsPath = (_document$querySelect = document.querySelector('script[src*="/base.js"]')) === null || _document$querySelect === void 0 ? void 0 : _document$querySelect.src;
  371.  
  372. if (!playerBaseJsPath) return;
  373.  
  374. const xmlhttp = new XMLHttpRequest();
  375. xmlhttp.open("GET", playerBaseJsPath, false);
  376. xmlhttp.send(null);
  377.  
  378. return parseInt(xmlhttp.responseText.match(/signatureTimestamp:([0-9]*)/)[1]);
  379. })();
  380. }
  381.  
  382. function sendInnertubeRequest(endpoint, payload, useAuth) {
  383. const xmlhttp = new XMLHttpRequest();
  384. xmlhttp.open("POST", `/youtubei/${endpoint}?key=${getYtcfgValue('INNERTUBE_API_KEY')}`, false);
  385. if (useAuth && isUserLoggedIn()) {
  386. xmlhttp.withCredentials = true;
  387. xmlhttp.setRequestHeader("Authorization", generateSidBasedAuth());
  388. }
  389. xmlhttp.send(JSON.stringify(payload));
  390. return nativeJSONParse(xmlhttp.responseText);
  391. }
  392.  
  393. function getInnertubeEmbedPayload(videoId, clientConfig, playlistId, playlistIndex) {
  394. return {
  395. context: {
  396. client: {
  397. ...getYtcfgValue('INNERTUBE_CONTEXT').client,
  398. ...{ clientName: getMainPageClientName() },
  399. ...(clientConfig || {}) },
  400.  
  401. thirdParty: {
  402. embedUrl: "https://www.youtube.com/" } },
  403.  
  404.  
  405. playbackContext: {
  406. contentPlaybackContext: {
  407. signatureTimestamp: getSignatureTimestamp() } },
  408.  
  409.  
  410. videoId,
  411. playlistId,
  412. playlistIndex };
  413.  
  414. }
  415.  
  416. function getSidCookie() {
  417. return getCookie('SAPISID') || getCookie('__Secure-3PAPISID');
  418. }
  419.  
  420. function generateSidBasedAuth() {
  421. const sid = getSidCookie();
  422. const timestamp = Math.floor(new Date().getTime() / 1000);
  423. const input = timestamp + " " + sid + " " + location.origin;
  424. const hash = generateSha1Hash(input);
  425. return `SAPISIDHASH ${timestamp}_${hash}`;
  426. }
  427.  
  428. const logPrefix = "Simple-YouTube-Age-Restriction-Bypass:";
  429. const logSuffix = "You can report bugs at: https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues";
  430.  
  431. function error(err, msg) {
  432. console.error(logPrefix, msg, err, getYtcfgDebugString(), logSuffix);
  433. }
  434.  
  435. function info(msg) {
  436. console.info(logPrefix, msg);
  437. }
  438.  
  439. function getYtcfgDebugString() {
  440. try {
  441. return `InnertubeConfig: ` +
  442. `innertubeApiKey: ${getYtcfgValue('INNERTUBE_API_KEY')} ` +
  443. `innertubeClientName: ${getYtcfgValue('INNERTUBE_CLIENT_NAME')} ` +
  444. `innertubeClientVersion: ${getYtcfgValue('INNERTUBE_CLIENT_VERSION')} ` +
  445. `loggedIn: ${getYtcfgValue('LOGGED_IN')} `;
  446. } catch (err) {
  447. return `Failed to access config: ${err}`;
  448. }
  449. }
  450.  
  451. function getGoogleVideoUrl(originalUrl, proxyHost) {
  452. return proxyHost + "/direct/" + btoa(originalUrl);
  453. }
  454.  
  455. function getPlayer(videoId, reason) {
  456. const queryParams = new URLSearchParams({
  457. videoId,
  458. reason,
  459. clientName: getMainPageClientName(),
  460. clientVersion: getYtcfgValue('INNERTUBE_CLIENT_VERSION'),
  461. signatureTimestamp: getSignatureTimestamp(),
  462. isEmbed: +isEmbed }).
  463. toString();
  464.  
  465. const proxyUrl = ACCOUNT_PROXY_SERVER_HOST + '/getPlayer?' + queryParams;
  466.  
  467. const xmlhttp = new XMLHttpRequest();
  468. xmlhttp.open('GET', proxyUrl, false);
  469. xmlhttp.send(null);
  470.  
  471. const playerResponse = nativeJSONParse(xmlhttp.responseText);
  472.  
  473. // mark request as 'proxied'
  474. playerResponse.proxied = true;
  475.  
  476. return playerResponse;
  477. }
  478.  
  479. var tDesktop = "<tp-yt-paper-toast></tp-yt-paper-toast>\r\n";
  480.  
  481. var tMobile = "<c3-toast>\r\n <ytm-notification-action-renderer>\r\n <div class=\"notification-action-response-text\"></div>\r\n </ytm-notification-action-renderer>\r\n</c3-toast>\r\n";
  482.  
  483. const pageLoad = new Deferred();
  484. const pageLoadEventName = isDesktop ? 'yt-navigate-finish' : 'state-navigateend';
  485.  
  486. const template = isDesktop ? tDesktop : tMobile;
  487.  
  488. const nNotificationWrapper = createElement('div', { id: 'notification-wrapper', innerHTML: template });
  489. const nNotification = nNotificationWrapper.querySelector(':scope > *');
  490. const nMobileText = !isDesktop && nNotification.querySelector('.notification-action-response-text');
  491.  
  492. window.addEventListener(pageLoadEventName, init, { once: true });
  493.  
  494. function init() {
  495. document.body.append(nNotificationWrapper);
  496. pageLoad.resolve();
  497. }
  498.  
  499. function show(message, duration = 5) {
  500.  
  501. pageLoad.then(_show);
  502.  
  503. function _show() {
  504. const _duration = duration * 1000;
  505. if (isDesktop) {
  506. nNotification.duration = _duration;
  507. nNotification.show(message);
  508. } else {
  509. nMobileText.innerText = message;
  510. nNotification.setAttribute('dir', 'in');
  511. setTimeout(() => {
  512. nNotification.setAttribute('dir', 'out');
  513. }, _duration + 225);
  514. }
  515. }
  516. }
  517.  
  518. var Notification = { show };
  519.  
  520. const messagesMap = {
  521. success: "Age-restricted video successfully unlocked!",
  522. fail: "Unable to unlock this video 🙁 - More information in the developer console" };
  523.  
  524.  
  525. const unlockStrategies = [
  526. // Strategy 1: Retrieve the video info by using a age-gate bypass for the innertube API
  527. // Source: https://github.com/yt-dlp/yt-dlp/issues/574#issuecomment-887171136
  528. {
  529. name: 'Innertube Embed',
  530. requireAuth: false,
  531. fn: (videoId) => getPlayer$1(videoId, { clientScreen: 'EMBED' }, false) },
  532.  
  533. // Strategy 2: Retrieve the video info by using the WEB_CREATOR client in combination with user authentication
  534. // See https://github.com/yt-dlp/yt-dlp/pull/600
  535. {
  536. name: 'Innertube Creator + Auth',
  537. requireAuth: true,
  538. fn: (videoId) => getPlayer$1(videoId, { clientName: 'WEB_CREATOR', clientVersion: '1.20210909.07.00' }, true) },
  539.  
  540. // Strategy 3: Retrieve the video info from an account proxy server.
  541. // See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/tree/main/account-proxy
  542. {
  543. name: 'Account Proxy',
  544. requireAuth: false,
  545. fn: (videoId, reason) => getPlayer(videoId, reason) }];
  546.  
  547.  
  548.  
  549. let lastProxiedGoogleVideoUrlParams;
  550. let responseCache = {};
  551.  
  552. function getLastProxiedGoogleVideoId() {var _lastProxiedGoogleVid;
  553. return (_lastProxiedGoogleVid = lastProxiedGoogleVideoUrlParams) === null || _lastProxiedGoogleVid === void 0 ? void 0 : _lastProxiedGoogleVid.get("id");
  554. }
  555.  
  556. function unlockPlayerResponse(playerResponse) {var _playerResponse$video, _playerResponse$playa, _playerResponse$previ, _unlockedPlayerRespon, _unlockedPlayerRespon3;
  557. const videoId = ((_playerResponse$video = playerResponse.videoDetails) === null || _playerResponse$video === void 0 ? void 0 : _playerResponse$video.videoId) || getYtcfgValue("PLAYER_VARS").video_id;
  558. const reason = ((_playerResponse$playa = playerResponse.playabilityStatus) === null || _playerResponse$playa === void 0 ? void 0 : _playerResponse$playa.status) || ((_playerResponse$previ = playerResponse.previewPlayabilityStatus) === null || _playerResponse$previ === void 0 ? void 0 : _playerResponse$previ.status);
  559. const unlockedPlayerResponse = getUnlockedPlayerResponse(videoId, reason);
  560.  
  561. // account proxy error?
  562. if (unlockedPlayerResponse.errorMessage) {
  563. Notification.show(`${messagesMap.fail} (ProxyError)`, 10);
  564. throw new Error(`Player Unlock Failed, Proxy Error Message: ${unlockedPlayerResponse.errorMessage}`);
  565. }
  566.  
  567. // check if the unlocked response isn't playable
  568. if (((_unlockedPlayerRespon = unlockedPlayerResponse.playabilityStatus) === null || _unlockedPlayerRespon === void 0 ? void 0 : _unlockedPlayerRespon.status) !== "OK") {var _unlockedPlayerRespon2;
  569. Notification.show(`${messagesMap.fail} (PlayabilityError)`, 10);
  570. throw new Error(`Player Unlock Failed, playabilityStatus: ${(_unlockedPlayerRespon2 = unlockedPlayerResponse.playabilityStatus) === null || _unlockedPlayerRespon2 === void 0 ? void 0 : _unlockedPlayerRespon2.status}`);
  571. }
  572.  
  573. // if the video info was retrieved via proxy, store the URL params from the url-attribute to detect later if the requested video file (googlevideo.com) need a proxy.
  574. if (unlockedPlayerResponse.proxied && (_unlockedPlayerRespon3 = unlockedPlayerResponse.streamingData) !== null && _unlockedPlayerRespon3 !== void 0 && _unlockedPlayerRespon3.adaptiveFormats) {var _unlockedPlayerRespon4, _unlockedPlayerRespon5;
  575. const cipherText = (_unlockedPlayerRespon4 = unlockedPlayerResponse.streamingData.adaptiveFormats.find((x) => x.signatureCipher)) === null || _unlockedPlayerRespon4 === void 0 ? void 0 : _unlockedPlayerRespon4.signatureCipher;
  576. const videoUrl = cipherText ? new URLSearchParams(cipherText).get("url") : (_unlockedPlayerRespon5 = unlockedPlayerResponse.streamingData.adaptiveFormats.find((x) => x.url)) === null || _unlockedPlayerRespon5 === void 0 ? void 0 : _unlockedPlayerRespon5.url;
  577.  
  578. lastProxiedGoogleVideoUrlParams = videoUrl ? new URLSearchParams(new URL(videoUrl).search) : null;
  579. }
  580.  
  581. // Overwrite the embedded (preview) playabilityStatus with the unlocked one
  582. if (playerResponse.previewPlayabilityStatus) {
  583. playerResponse.previewPlayabilityStatus = unlockedPlayerResponse.playabilityStatus;
  584. }
  585.  
  586. // Transfer all unlocked properties to the original player response
  587. Object.assign(playerResponse, unlockedPlayerResponse);
  588.  
  589. Notification.show(messagesMap.success);
  590. }
  591.  
  592. function getUnlockedPlayerResponse(videoId, reason) {
  593. // Check if response is cached
  594. if (responseCache.videoId === videoId) return responseCache.playerResponse;
  595.  
  596. let playerResponse;
  597.  
  598. unlockStrategies.every((strategy, index) => {var _playerResponse, _playerResponse$playa2;
  599. if (strategy.requireAuth && !isUserLoggedIn()) return true;
  600.  
  601. info(`Trying Unlock Method #${index + 1} (${strategy.name})`);
  602.  
  603. playerResponse = strategy.fn(videoId, reason);
  604. return ((_playerResponse = playerResponse) === null || _playerResponse === void 0 ? void 0 : (_playerResponse$playa2 = _playerResponse.playabilityStatus) === null || _playerResponse$playa2 === void 0 ? void 0 : _playerResponse$playa2.status) !== "OK";
  605. });
  606.  
  607. // Cache response
  608. responseCache = { videoId, playerResponse };
  609.  
  610. return playerResponse;
  611. }
  612.  
  613. function unlockNextResponse(originalNextResponse) {
  614. info("Trying Sidebar Unlock Method (Innertube Embed)");
  615.  
  616. const { videoId, playlistId, index: playlistIndex } = originalNextResponse.currentVideoEndpoint.watchEndpoint;
  617. const unlockedNextResponse = getNext(videoId, { clientScreen: 'EMBED' }, playlistId, playlistIndex);
  618.  
  619. // check if the sidebar of the unlocked response is still empty
  620. if (isWatchNextSidebarEmpty(unlockedNextResponse)) {
  621. throw new Error(`Sidebar Unlock Failed`);
  622. }
  623.  
  624. // Transfer some parts of the unlocked response to the original response
  625. mergeNextResponse(originalNextResponse, unlockedNextResponse);
  626. }
  627.  
  628. function mergeNextResponse(originalNextResponse, unlockedNextResponse) {var _unlockedNextResponse, _unlockedNextResponse2, _unlockedNextResponse3, _unlockedNextResponse4, _unlockedNextResponse5;
  629. if (isDesktop) {
  630. // Transfer WatchNextResults to original response
  631. originalNextResponse.contents.twoColumnWatchNextResults.secondaryResults = unlockedNextResponse.contents.twoColumnWatchNextResults.secondaryResults;
  632.  
  633. // Transfer video description to original response
  634. const originalVideoSecondaryInfoRenderer = originalNextResponse.contents.twoColumnWatchNextResults.results.results.contents.
  635. find((x) => x.videoSecondaryInfoRenderer).videoSecondaryInfoRenderer;
  636. const unlockedVideoSecondaryInfoRenderer = unlockedNextResponse.contents.twoColumnWatchNextResults.results.results.contents.
  637. find((x) => x.videoSecondaryInfoRenderer).videoSecondaryInfoRenderer;
  638.  
  639. if (unlockedVideoSecondaryInfoRenderer.description)
  640. originalVideoSecondaryInfoRenderer.description = unlockedVideoSecondaryInfoRenderer.description;
  641.  
  642. return;
  643. }
  644.  
  645. // Transfer WatchNextResults to original response
  646. const unlockedWatchNextFeed = (_unlockedNextResponse = unlockedNextResponse.contents) === null || _unlockedNextResponse === void 0 ? void 0 : (_unlockedNextResponse2 = _unlockedNextResponse.singleColumnWatchNextResults) === null || _unlockedNextResponse2 === void 0 ? void 0 : (_unlockedNextResponse3 = _unlockedNextResponse2.results) === null || _unlockedNextResponse3 === void 0 ? void 0 : (_unlockedNextResponse4 = _unlockedNextResponse3.results) === null || _unlockedNextResponse4 === void 0 ? void 0 : (_unlockedNextResponse5 = _unlockedNextResponse4.contents) === null || _unlockedNextResponse5 === void 0 ? void 0 : _unlockedNextResponse5.
  647. find((x) => {var _x$itemSectionRendere;return ((_x$itemSectionRendere = x.itemSectionRenderer) === null || _x$itemSectionRendere === void 0 ? void 0 : _x$itemSectionRendere.targetId) === "watch-next-feed";});
  648.  
  649. if (unlockedWatchNextFeed)
  650. originalNextResponse.contents.singleColumnWatchNextResults.results.results.contents.push(unlockedWatchNextFeed);
  651.  
  652. // Transfer video description to original response
  653. const originalStructuredDescriptionContentRenderer = originalNextResponse.engagementPanels.
  654. find((x) => x.engagementPanelSectionListRenderer).engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.items.
  655. find((x) => x.expandableVideoDescriptionBodyRenderer);
  656. const unlockedStructuredDescriptionContentRenderer = unlockedNextResponse.engagementPanels.
  657. find((x) => x.engagementPanelSectionListRenderer).engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.items.
  658. find((x) => x.expandableVideoDescriptionBodyRenderer);
  659.  
  660. if (unlockedStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer)
  661. originalStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer = unlockedStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer;
  662. }
  663.  
  664. try {
  665. attachInitialDataInterceptor(checkAndUnlock);
  666. attachJsonInterceptor(checkAndUnlock);
  667. attachXhrOpenInterceptor(onXhrOpenCalled);
  668. } catch (err) {
  669. error(err, "Error while attaching data interceptors");
  670. }
  671.  
  672. function checkAndUnlock(ytData) {
  673.  
  674. try {
  675.  
  676. // Unlock #1: Initial page data structure and response from the '/youtubei/v1/player' endpoint
  677. if (isPlayerObject(ytData) && isAgeRestricted(ytData.playabilityStatus)) {
  678. unlockPlayerResponse(ytData);
  679. }
  680. // Unlock #2: Legacy response data structure (only used by m.youtube.com with &pbj=1)
  681. else if (isPlayerObject(ytData.playerResponse) && isAgeRestricted(ytData.playerResponse.playabilityStatus)) {
  682. unlockPlayerResponse(ytData.playerResponse);
  683. }
  684. // Unlock #3: Embedded Player inital data structure
  685. else if (isEmbeddedPlayerObject(ytData) && isAgeRestricted(ytData.previewPlayabilityStatus)) {
  686. unlockPlayerResponse(ytData);
  687. }
  688. // Equivelant of unlock #1 for sidebar/next response
  689. else if (isWatchNextObject(ytData) && isWatchNextSidebarEmpty(ytData)) {
  690. unlockNextResponse(ytData);
  691. }
  692. // Equivelant of unlock #2 for sidebar/next response
  693. else if (isWatchNextObject(ytData.response) && isWatchNextSidebarEmpty(ytData.response)) {
  694. unlockNextResponse(ytData.response);
  695. }
  696.  
  697. } catch (err) {
  698. error(err, "Video or sidebar unlock failed");
  699. }
  700.  
  701. return ytData;
  702. }
  703.  
  704. function onXhrOpenCalled(xhr, method, url) {
  705.  
  706. if (!isGoogleVideo(method, url)) return;
  707.  
  708. if (isGoogleVideoUnlockRequired(url, getLastProxiedGoogleVideoId())) {
  709.  
  710. // If the account proxy was used to retrieve the video info, the following applies:
  711. // 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.
  712. // to get around this, the googlevideo URL will be replaced with a web-proxy URL in the same country (US).
  713. // this is only required if the "gcr=[countrycode]" flag is set in the googlevideo-url...
  714.  
  715. // solve CORS errors by preventing YouTube from enabling the "withCredentials" option (required for the proxy)
  716. Object.defineProperty(xhr, "withCredentials", {
  717. set: () => {},
  718. get: () => false });
  719.  
  720.  
  721. return getGoogleVideoUrl(url.toString(), VIDEO_PROXY_SERVER_HOST);
  722. }
  723. }
  724.  
  725.  
  726. })(true);
  727.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement