Advertisement
Guest User

YOUTUBE BYPASS

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