Skip to content

GembelTechno/Simple-YouTube

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

Simple-YouTube

Simple YouTube Age Restriction

// ==UserScript== // @name Simple YouTube Age Restriction Bypass // @description Watch age restricted videos on YouTube without login and without age verification :) // @description:de Schaue YouTube Videos mit Altersbeschränkungen ohne Anmeldung und ohne dein Alter zu bestätigen :) // @description:fr Regardez des vidéos YouTube avec des restrictions d'âge sans vous inscrire et sans confirmer votre âge :) // @description:it Guarda i video con restrizioni di età su YouTube senza login e senza verifica dell'età :) // @version 2.2.2 // @author Zerody (https://github.com/zerodytrash) // @namespace https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/ // @supportURL https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues // @license MIT // @match https://www.youtube.com/* // @match https://m.youtube.com/* // @grant none // @run-at document-start // @compatible chrome Chrome + Tampermonkey or Violentmonkey // @compatible firefox Firefox + Greasemonkey or Tampermonkey or Violentmonkey // @compatible opera Opera + Tampermonkey or Violentmonkey // @compatible edge Edge + Tampermonkey or Violentmonkey // @compatible safari Safari + Tampermonkey or Violentmonkey // ==/UserScript==

/* This is a transpiled version to achieve a clean code base and better browser compatibility. You can find the nicely readable source code at https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass */

(function iife(inject) { // Trick to get around the sandbox restrictions in Greasemonkey (Firefox) // Inject code into the main window if criteria match if (typeof GM_info === "object" && GM_info.scriptHandler === "Greasemonkey" && inject) { window.eval("(" + iife.toString() + ")();"); return; }

// Script configuration variables const UNLOCKABLE_PLAYER_STATES = ['AGE_VERIFICATION_REQUIRED', 'AGE_CHECK_REQUIRED', 'LOGIN_REQUIRED']; const PLAYER_RESPONSE_ALIASES = ['ytInitialPlayerResponse', 'playerResponse'];

// The following proxies are currently used as fallback if the innertube age-gate bypass doesn't work... // You can host your own account proxy instance. See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/tree/main/account-proxy const ACCOUNT_PROXY_SERVER_HOST = 'https://youtube-proxy.zerody.one'; const VIDEO_PROXY_SERVER_HOST = 'https://phx.4everproxy.com';

const isDesktop = window.location.host !== 'm.youtube.com'; const isEmbed = window.location.pathname.includes('/embed/');

class Deferred { constructor() { return Object.assign( new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }), this);

}}

function createElement(tagName, options) { const node = document.createElement(tagName); options && Object.assign(node, options); return node; }

function isObject(obj) { return obj !== null && typeof obj === 'object'; }

function getCookie(name) { const value = ; ${document.cookie}; const parts = value.split(; ${name}=); if (parts.length === 2) return parts.pop().split(';').shift(); }

// Source: https://coursesweb.net/javascript/sha1-encrypt-data_cs function generateSha1Hash(msg) { function rotate_left(n, s) { var t4 = n << s | n >>> 32 - s; return t4; } function cvt_hex(val) { var str = ''; var i; var v; for (i = 7; i >= 0; i--) { v = val >>> i * 4 & 0x0f; str += v.toString(16); } return str; } function Utf8Encode(string) { string = string.replace(/\r\n/g, '\n'); var utftext = ''; for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if (c > 127 && c < 2048) { utftext += String.fromCharCode(c >> 6 | 192); utftext += String.fromCharCode(c & 63 | 128); } else { utftext += String.fromCharCode(c >> 12 | 224); utftext += String.fromCharCode(c >> 6 & 63 | 128); utftext += String.fromCharCode(c & 63 | 128); } } return utftext; } var blockstart; var i, j; var W = new Array(80); var H0 = 0x67452301; var H1 = 0xefcdab89; var H2 = 0x98badcfe; var H3 = 0x10325476; var H4 = 0xc3d2e1f0; var A, B, C, D, E; var temp; msg = Utf8Encode(msg); var msg_len = msg.length; var word_array = new Array(); for (i = 0; i < msg_len - 3; i += 4) { j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 | msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3); word_array.push(j); } switch (msg_len % 4) { case 0: i = 0x080000000; break; case 1: i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000; break; case 2: i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000; break; case 3: i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80; break;}

word_array.push(i);
while (word_array.length % 16 != 14) word_array.push(0);
word_array.push(msg_len >>> 29);
word_array.push(msg_len << 3 & 0x0ffffffff);
for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
  for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i];
  for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
  A = H0;
  B = H1;
  C = H2;
  D = H3;
  E = H4;
  for (i = 0; i <= 19; i++) {
    temp = rotate_left(A, 5) + (B & C | ~B & D) + E + W[i] + 0x5a827999 & 0x0ffffffff;
    E = D;
    D = C;
    C = rotate_left(B, 30);
    B = A;
    A = temp;
  }
  for (i = 20; i <= 39; i++) {
    temp = rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ed9eba1 & 0x0ffffffff;
    E = D;
    D = C;
    C = rotate_left(B, 30);
    B = A;
    A = temp;
  }
  for (i = 40; i <= 59; i++) {
    temp = rotate_left(A, 5) + (B & C | B & D | C & D) + E + W[i] + 0x8f1bbcdc & 0x0ffffffff;
    E = D;
    D = C;
    C = rotate_left(B, 30);
    B = A;
    A = temp;
  }
  for (i = 60; i <= 79; i++) {
    temp = rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xca62c1d6 & 0x0ffffffff;
    E = D;
    D = C;
    C = rotate_left(B, 30);
    B = A;
    A = temp;
  }
  H0 = H0 + A & 0x0ffffffff;
  H1 = H1 + B & 0x0ffffffff;
  H2 = H2 + C & 0x0ffffffff;
  H3 = H3 + D & 0x0ffffffff;
  H4 = H4 + E & 0x0ffffffff;
}

return (cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4)).toLowerCase();

}

const nativeJSONParse = window.JSON.parse;

const nativeXMLHttpRequestOpen = XMLHttpRequest.prototype.open;

// Some extensions like AdBlock override the Object.defineProperty function to prevent a redefinition of the 'ytInitialPlayerResponse' variable by YouTube. // 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 :( // To solve this problem the native defineProperty function will be retrieved from another window (iframe) const nativeObjectDefineProperty = (() => { // Check if function is native if (Object.defineProperty.toString().includes('[native code]')) { return Object.defineProperty; }

// If function is overidden, restore the native function from another window...
const tempFrame = createElement('iframe', { style: `display: none;` });
document.documentElement.append(tempFrame);

const native = tempFrame.contentWindow.Object.defineProperty;

tempFrame.remove();

return native;

})();

let wrappedPlayerResponse; let wrappedNextResponse;

function attachInitialDataInterceptor(onInititalDataSet) { // Just for compatibility: Backup original getter/setter for 'ytInitialPlayerResponse', defined by other extensions like AdBlock let { get: chainedPlayerGetter, set: chainedPlayerSetter } = Object.getOwnPropertyDescriptor(window, 'ytInitialPlayerResponse') || {};

// 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
Object.defineProperty = (obj, prop, descriptor) => {
  if (obj === window && PLAYER_RESPONSE_ALIASES.includes(prop)) {
    console.info("Another extension tries to redefine '" + prop + "' (probably an AdBlock extension). Chain it...");

    if (descriptor !== null && descriptor !== void 0 && descriptor.set) chainedPlayerSetter = descriptor.set;
    if (descriptor !== null && descriptor !== void 0 && descriptor.get) chainedPlayerGetter = descriptor.get;
  } else {
    nativeObjectDefineProperty(obj, prop, descriptor);
  }
};

// Redefine 'ytInitialPlayerResponse' to inspect and modify the initial player response as soon as the variable is set on page load
nativeObjectDefineProperty(window, 'ytInitialPlayerResponse', {
  set: (playerResponse) => {
    // prevent recursive setter calls by ignoring unchanged data (this fixes a problem caused by Brave browser shield)
    if (playerResponse === wrappedPlayerResponse) return;

    wrappedPlayerResponse = isObject(playerResponse) ? onInititalDataSet(playerResponse) : playerResponse;
    if (typeof chainedPlayerSetter === 'function') chainedPlayerSetter(wrappedPlayerResponse);
  },
  get: () => {
    // eslint-disable-next-line no-empty
    if (typeof chainedPlayerGetter === 'function')
    try {
      return chainedPlayerGetter();
    } catch (err) {
      // ignore the error
    }
    return wrappedPlayerResponse || {};
  },
  configurable: true });


// Also redefine 'ytInitialData' for the initial next/sidebar response
nativeObjectDefineProperty(window, 'ytInitialData', {
  set: (nextResponse) => {
    wrappedNextResponse = isObject(nextResponse) ? onInititalDataSet(nextResponse) : nextResponse;
  },
  get: () => wrappedNextResponse,
  configurable: true });

}

// Intercept, inspect and modify JSON-based communication to unlock player responses by hijacking the JSON.parse function function attachJsonInterceptor(onJsonDataReceived) { window.JSON.parse = (text, reviver) => { const data = nativeJSONParse(text, reviver); return !isObject(data) ? data : onJsonDataReceived(data); }; }

function attachXhrOpenInterceptor(onXhrOpenCalled) { XMLHttpRequest.prototype.open = function (method, url) { if (arguments.length > 1 && typeof url === 'string' && url.indexOf('https://') === 0) { const modifiedUrl = onXhrOpenCalled(this, method, new URL(url));

    if (typeof modifiedUrl === 'string') {
      url = modifiedUrl;
    }
  }

  nativeXMLHttpRequestOpen.apply(this, arguments);
};

}

function isPlayerObject(parsedData) { return (parsedData === null || parsedData === void 0 ? void 0 : parsedData.videoDetails) && (parsedData === null || parsedData === void 0 ? void 0 : parsedData.playabilityStatus); }

function isEmbeddedPlayerObject(parsedData) { return typeof (parsedData === null || parsedData === void 0 ? void 0 : parsedData.previewPlayabilityStatus) === 'object'; }

function isAgeRestricted(playabilityStatus) {var _playabilityStatus$er, _playabilityStatus$er2, _playabilityStatus$er3, _playabilityStatus$er4, _playabilityStatus$er5, _playabilityStatus$er6, _playabilityStatus$er7, _playabilityStatus$er8; if (!(playabilityStatus !== null && playabilityStatus !== void 0 && playabilityStatus.status)) return false; if (playabilityStatus.desktopLegacyAgeGateReason) return true; if (UNLOCKABLE_PLAYER_STATES.includes(playabilityStatus.status)) return true;

// Fix to detect age restrictions on embed player
// see https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues/85#issuecomment-946853553
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.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')));

}

function isWatchNextObject(parsedData) {var _parsedData$currentVi, _parsedData$currentVi2; 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; return !!parsedData.contents.twoColumnWatchNextResults || !!parsedData.contents.singleColumnWatchNextResults; }

function isWatchNextSidebarEmpty(parsedData) {var _parsedData$contents2, _parsedData$contents3, _parsedData$contents4, _parsedData$contents5, _content$find; if (isDesktop) {var _parsedData$contents, _parsedData$contents$, _parsedData$contents$2, _parsedData$contents$3; // WEB response layout 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; return !result; }

// MWEB response layout
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;
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;
return typeof result !== 'object';

}

function isGoogleVideo(method, url) { return method === 'GET' && url.host.includes('.googlevideo.com'); }

function isGoogleVideoUnlockRequired(googleVideoUrl, lastProxiedGoogleVideoId) { const urlParams = new URLSearchParams(googleVideoUrl.search); const hasGcrFlag = urlParams.get('gcr'); const wasUnlockedByAccountProxy = urlParams.get('id') === lastProxiedGoogleVideoId;

return hasGcrFlag && wasUnlockedByAccountProxy;

}

function getYtcfgValue(value) {var _window$ytcfg; return (_window$ytcfg = window.ytcfg) === null || _window$ytcfg === void 0 ? void 0 : _window$ytcfg.get(value); }

function isUserLoggedIn() { // Session Cookie exists? if (!getSidCookie()) return false;

// LOGGED_IN doesn't exist on embedded page, use DELEGATED_SESSION_ID as fallback
if (typeof getYtcfgValue('LOGGED_IN') === 'boolean') return getYtcfgValue('LOGGED_IN');
if (typeof getYtcfgValue('DELEGATED_SESSION_ID') === 'string') return true;

return false;

}

function getPlayer$1(videoId, clientConfig, useAuth) { const payload = getInnertubeEmbedPayload(videoId, clientConfig); return sendInnertubeRequest('v1/player', payload, useAuth); }

function getNext(videoId, clientConfig, playlistId, playlistIndex) { const payload = getInnertubeEmbedPayload(videoId, clientConfig, playlistId, playlistIndex); return sendInnertubeRequest('v1/next', payload, false); }

function getMainPageClientName() { // replace embedded client with YouTube's main page client (e.g. WEB_EMBEDDED_PLAYER => WEB) return getYtcfgValue('INNERTUBE_CLIENT_NAME').replace('_EMBEDDED_PLAYER', ''); }

function getSignatureTimestamp() { return ( getYtcfgValue('STS') || (() => {var _document$querySelect; // STS is missing on embedded player. Retrieve from player base script as fallback... const playerBaseJsPath = (_document$querySelect = document.querySelector('script[src*="/base.js"]')) === null || _document$querySelect === void 0 ? void 0 : _document$querySelect.src;

    if (!playerBaseJsPath) return;

    const xmlhttp = new XMLHttpRequest();
    xmlhttp.open('GET', playerBaseJsPath, false);
    xmlhttp.send(null);

    return parseInt(xmlhttp.responseText.match(/signatureTimestamp:([0-9]*)/)[1]);
  })());

}

function sendInnertubeRequest(endpoint, payload, useAuth) { const xmlhttp = new XMLHttpRequest(); xmlhttp.open('POST', /youtubei/${endpoint}?key=${getYtcfgValue('INNERTUBE_API_KEY')}, false); if (useAuth && isUserLoggedIn()) { xmlhttp.withCredentials = true; xmlhttp.setRequestHeader('Authorization', generateSidBasedAuth()); } xmlhttp.send(JSON.stringify(payload)); return nativeJSONParse(xmlhttp.responseText); }

function getInnertubeEmbedPayload(videoId, clientConfig, playlistId, playlistIndex) { return { context: { client: { ...getYtcfgValue('INNERTUBE_CONTEXT').client, ...{ clientName: getMainPageClientName() }, ...(clientConfig || {}) },

    thirdParty: {
      embedUrl: 'https://www.youtube.com/' } },


  playbackContext: {
    contentPlaybackContext: {
      signatureTimestamp: getSignatureTimestamp() } },


  videoId,
  playlistId,
  playlistIndex };

}

function getSidCookie() { return getCookie('SAPISID') || getCookie('__Secure-3PAPISID'); }

function generateSidBasedAuth() { const sid = getSidCookie(); const timestamp = Math.floor(new Date().getTime() / 1000); const input = timestamp + ' ' + sid + ' ' + location.origin; const hash = generateSha1Hash(input); return SAPISIDHASH ${timestamp}_${hash}; }

const logPrefix = 'Simple-YouTube-Age-Restriction-Bypass:'; const logSuffix = 'You can report bugs at: https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues';

function error(err, msg) { console.error(logPrefix, msg, err, getYtcfgDebugString(), logSuffix); }

function info(msg) { console.info(logPrefix, msg); }

function getYtcfgDebugString() { try { return ( InnertubeConfig: + innertubeApiKey: ${getYtcfgValue('INNERTUBE_API_KEY')} + innertubeClientName: ${getYtcfgValue('INNERTUBE_CLIENT_NAME')} + innertubeClientVersion: ${getYtcfgValue('INNERTUBE_CLIENT_VERSION')} + loggedIn: ${getYtcfgValue('LOGGED_IN')} );

} catch (err) {
  return `Failed to access config: ${err}`;
}

}

function getGoogleVideoUrl(originalUrl, proxyHost) { return proxyHost + '/direct/' + btoa(originalUrl); }

function getPlayer(videoId, reason) { const queryParams = new URLSearchParams({ videoId, reason, clientName: getMainPageClientName(), clientVersion: getYtcfgValue('INNERTUBE_CLIENT_VERSION'), signatureTimestamp: getSignatureTimestamp(), isEmbed: +isEmbed }). toString();

const proxyUrl = ACCOUNT_PROXY_SERVER_HOST + '/getPlayer?' + queryParams;

const xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', proxyUrl, false);
xmlhttp.send(null);

const playerResponse = nativeJSONParse(xmlhttp.responseText);

// mark request as 'proxied'
playerResponse.proxied = true;

return playerResponse;

}

var tDesktop = "\n";

var tMobile = "\n \n <div class="notification-action-response-text">\n \n\n";

const pageLoad = new Deferred(); const pageLoadEventName = isDesktop ? 'yt-navigate-finish' : 'state-navigateend';

const template = isDesktop ? tDesktop : tMobile;

const nNotificationWrapper = createElement('div', { id: 'notification-wrapper', innerHTML: template }); const nNotification = nNotificationWrapper.querySelector(':scope > *'); const nMobileText = !isDesktop && nNotification.querySelector('.notification-action-response-text');

window.addEventListener(pageLoadEventName, init, { once: true });

function init() { document.body.append(nNotificationWrapper); pageLoad.resolve(); }

function show(message, duration = 5) {

pageLoad.then(_show);

function _show() {
  const _duration = duration * 1000;
  if (isDesktop) {
    nNotification.duration = _duration;
    nNotification.show(message);
  } else {
    nMobileText.innerText = message;
    nNotification.setAttribute('dir', 'in');
    setTimeout(() => {
      nNotification.setAttribute('dir', 'out');
    }, _duration + 225);
  }
}

}

var Notification = { show };

const messagesMap = { success: 'Age-restricted video successfully unlocked!', fail: 'Unable to unlock this video 🙁 - More information in the developer console' };

const unlockStrategies = [ // Strategy 1: Retrieve the video info by using a age-gate bypass for the innertube API // Source: yt-dlp/yt-dlp#574 (comment) { name: 'Innertube Embed', requireAuth: false, fn: (videoId) => getPlayer$1(videoId, { clientScreen: 'EMBED' }, false) },

// Strategy 2: Retrieve the video info by using the WEB_CREATOR client in combination with user authentication // See yt-dlp/yt-dlp#600 { name: 'Innertube Creator + Auth', requireAuth: true, fn: (videoId) => getPlayer$1(videoId, { clientName: 'WEB_CREATOR', clientVersion: '1.20210909.07.00' }, true) },

// Strategy 3: Retrieve the video info from an account proxy server. // See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/tree/main/account-proxy { name: 'Account Proxy', requireAuth: false, fn: (videoId, reason) => getPlayer(videoId, reason) }];

let lastProxiedGoogleVideoUrlParams; let responseCache = {};

function getLastProxiedGoogleVideoId() {var _lastProxiedGoogleVid; return (_lastProxiedGoogleVid = lastProxiedGoogleVideoUrlParams) === null || _lastProxiedGoogleVid === void 0 ? void 0 : _lastProxiedGoogleVid.get('id'); }

function unlockPlayerResponse(playerResponse) {var _playerResponse$video, _playerResponse$playa, _playerResponse$previ, _unlockedPlayerRespon, _unlockedPlayerRespon3; const videoId = ((_playerResponse$video = playerResponse.videoDetails) === null || _playerResponse$video === void 0 ? void 0 : _playerResponse$video.videoId) || getYtcfgValue('PLAYER_VARS').video_id; 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); const unlockedPlayerResponse = getUnlockedPlayerResponse(videoId, reason);

// account proxy error?
if (unlockedPlayerResponse.errorMessage) {
  Notification.show(`${messagesMap.fail} (ProxyError)`, 10);
  throw new Error(`Player Unlock Failed, Proxy Error Message: ${unlockedPlayerResponse.errorMessage}`);
}

// check if the unlocked response isn't playable
if (((_unlockedPlayerRespon = unlockedPlayerResponse.playabilityStatus) === null || _unlockedPlayerRespon === void 0 ? void 0 : _unlockedPlayerRespon.status) !== 'OK') {var _unlockedPlayerRespon2;
  Notification.show(`${messagesMap.fail} (PlayabilityError)`, 10);
  throw new Error(`Player Unlock Failed, playabilityStatus: ${(_unlockedPlayerRespon2 = unlockedPlayerResponse.playabilityStatus) === null || _unlockedPlayerRespon2 === void 0 ? void 0 : _unlockedPlayerRespon2.status}`);
}

// 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.
if (unlockedPlayerResponse.proxied && (_unlockedPlayerRespon3 = unlockedPlayerResponse.streamingData) !== null && _unlockedPlayerRespon3 !== void 0 && _unlockedPlayerRespon3.adaptiveFormats) {var _unlockedPlayerRespon4, _unlockedPlayerRespon5;
  const cipherText = (_unlockedPlayerRespon4 = unlockedPlayerResponse.streamingData.adaptiveFormats.find((x) => x.signatureCipher)) === null || _unlockedPlayerRespon4 === void 0 ? void 0 : _unlockedPlayerRespon4.signatureCipher;
  const videoUrl = cipherText ? new URLSearchParams(cipherText).get('url') : (_unlockedPlayerRespon5 = unlockedPlayerResponse.streamingData.adaptiveFormats.find((x) => x.url)) === null || _unlockedPlayerRespon5 === void 0 ? void 0 : _unlockedPlayerRespon5.url;

  lastProxiedGoogleVideoUrlParams = videoUrl ? new URLSearchParams(new URL(videoUrl).search) : null;
}

// Overwrite the embedded (preview) playabilityStatus with the unlocked one
if (playerResponse.previewPlayabilityStatus) {
  playerResponse.previewPlayabilityStatus = unlockedPlayerResponse.playabilityStatus;
}

// Transfer all unlocked properties to the original player response
Object.assign(playerResponse, unlockedPlayerResponse);

Notification.show(messagesMap.success);

}

function getUnlockedPlayerResponse(videoId, reason) { // Check if response is cached if (responseCache.videoId === videoId) return responseCache.playerResponse;

let playerResponse;

unlockStrategies.every((strategy, index) => {var _playerResponse, _playerResponse$playa2;
  if (strategy.requireAuth && !isUserLoggedIn()) return true;

  info(`Trying Unlock Method #${index + 1} (${strategy.name})`);

  playerResponse = strategy.fn(videoId, reason);
  return ((_playerResponse = playerResponse) === null || _playerResponse === void 0 ? void 0 : (_playerResponse$playa2 = _playerResponse.playabilityStatus) === null || _playerResponse$playa2 === void 0 ? void 0 : _playerResponse$playa2.status) !== 'OK';
});

// Cache response
responseCache = { videoId, playerResponse };

return playerResponse;

}

function unlockNextResponse(originalNextResponse) { info('Trying Sidebar Unlock Method (Innertube Embed)');

const { videoId, playlistId, index: playlistIndex } = originalNextResponse.currentVideoEndpoint.watchEndpoint;
const unlockedNextResponse = getNext(videoId, { clientScreen: 'EMBED' }, playlistId, playlistIndex);

// check if the sidebar of the unlocked response is still empty
if (isWatchNextSidebarEmpty(unlockedNextResponse)) {
  throw new Error(`Sidebar Unlock Failed`);
}

// Transfer some parts of the unlocked response to the original response
mergeNextResponse(originalNextResponse, unlockedNextResponse);

}

function mergeNextResponse(originalNextResponse, unlockedNextResponse) {var _unlockedNextResponse, _unlockedNextResponse2, _unlockedNextResponse3, _unlockedNextResponse4, _unlockedNextResponse5; if (isDesktop) { // Transfer WatchNextResults to original response originalNextResponse.contents.twoColumnWatchNextResults.secondaryResults = unlockedNextResponse.contents.twoColumnWatchNextResults.secondaryResults;

  // Transfer video description to original response
  const originalVideoSecondaryInfoRenderer = originalNextResponse.contents.twoColumnWatchNextResults.results.results.contents.find(
  (x) => x.videoSecondaryInfoRenderer).
  videoSecondaryInfoRenderer;
  const unlockedVideoSecondaryInfoRenderer = unlockedNextResponse.contents.twoColumnWatchNextResults.results.results.contents.find(
  (x) => x.videoSecondaryInfoRenderer).
  videoSecondaryInfoRenderer;

  if (unlockedVideoSecondaryInfoRenderer.description) originalVideoSecondaryInfoRenderer.description = unlockedVideoSecondaryInfoRenderer.description;

  return;
}

// Transfer WatchNextResults to original response
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.find(
(x) => {var _x$itemSectionRendere;return ((_x$itemSectionRendere = x.itemSectionRenderer) === null || _x$itemSectionRendere === void 0 ? void 0 : _x$itemSectionRendere.targetId) === 'watch-next-feed';});


if (unlockedWatchNextFeed) originalNextResponse.contents.singleColumnWatchNextResults.results.results.contents.push(unlockedWatchNextFeed);

// Transfer video description to original response
const originalStructuredDescriptionContentRenderer = originalNextResponse.engagementPanels.
find((x) => x.engagementPanelSectionListRenderer).
engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.items.find((x) => x.expandableVideoDescriptionBodyRenderer);
const unlockedStructuredDescriptionContentRenderer = unlockedNextResponse.engagementPanels.
find((x) => x.engagementPanelSectionListRenderer).
engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.items.find((x) => x.expandableVideoDescriptionBodyRenderer);

if (unlockedStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer)
originalStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer = unlockedStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer;

}

try { attachInitialDataInterceptor(checkAndUnlock); attachJsonInterceptor(checkAndUnlock); attachXhrOpenInterceptor(onXhrOpenCalled); } catch (err) { error(err, 'Error while attaching data interceptors'); }

function checkAndUnlock(ytData) { try { // Unlock #1: Initial page data structure and response from the '/youtubei/v1/player' endpoint if (isPlayerObject(ytData) && isAgeRestricted(ytData.playabilityStatus)) { unlockPlayerResponse(ytData); } // Unlock #2: Legacy response data structure (only used by m.youtube.com with &pbj=1) else if (isPlayerObject(ytData.playerResponse) && isAgeRestricted(ytData.playerResponse.playabilityStatus)) { unlockPlayerResponse(ytData.playerResponse); } // Unlock #3: Embedded Player inital data structure else if (isEmbeddedPlayerObject(ytData) && isAgeRestricted(ytData.previewPlayabilityStatus)) { unlockPlayerResponse(ytData); } // Equivelant of unlock #1 for sidebar/next response else if (isWatchNextObject(ytData) && isWatchNextSidebarEmpty(ytData)) { unlockNextResponse(ytData); } // Equivelant of unlock #2 for sidebar/next response else if (isWatchNextObject(ytData.response) && isWatchNextSidebarEmpty(ytData.response)) { unlockNextResponse(ytData.response); } } catch (err) { error(err, 'Video or sidebar unlock failed'); }

return ytData;

}

function onXhrOpenCalled(xhr, method, url) { if (!isGoogleVideo(method, url)) return;

if (isGoogleVideoUnlockRequired(url, getLastProxiedGoogleVideoId())) {
  // If the account proxy was used to retrieve the video info, the following applies:
  // 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.
  // to get around this, the googlevideo URL will be replaced with a web-proxy URL in the same country (US).
  // this is only required if the "gcr=[countrycode]" flag is set in the googlevideo-url...

  // solve CORS errors by preventing YouTube from enabling the "withCredentials" option (required for the proxy)
  Object.defineProperty(xhr, 'withCredentials', {
    set: () => {},
    get: () => false });


  return getGoogleVideoUrl(url.toString(), VIDEO_PROXY_SERVER_HOST);
}

}

})(true);

About

Simple YouTube Age Restriction

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published