Simple YouTube Age Restriction Bypass
Simple YouTube Age Restriction Bypass
Simple YouTube Age Restriction Bypass
/*
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;
}
// 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';
// Whether a thumbnail is blurred can be detected by the following "sqp"
parameter values in the thumbnail URL.
// Seems to be base64 encoded protobuf objects, see
https://stackoverflow.com/a/51203860
const THUMBNAIL_BLURRED_SQPS = [
'-oaymwEpCOADEI4CSFryq4qpAxsIARUAAAAAGAElAADIQj0AgKJDeAHtAZmZGUI=', // Desktop
480x270
'-oaymwEiCOADEI4CSFXyq4qpAxQIARUAAIhCGAFwAcABBu0BmZkZQg==', // Desktop 480x270
'-oaymwEiCOgCEMoBSFXyq4qpAxQIARUAAIhCGAFwAcABBu0BZmbmQQ==', // Desktop 360x202
'-oaymwEiCNAFEJQDSFXyq4qpAxQIARUAAIhCGAFwAcABBu0BZmZmQg==', // Desktop 720x404
'-oaymwEdCNAFEJQDSFryq4qpAw8IARUAAIhCGAHtAWZmZkI=', // Desktop 720x404
'-oaymwEdCNACELwBSFryq4qpAw8IARUAAIhCGAHtAT0K10E=', // Desktop 336x188
'-oaymwESCMACELQB8quKqQMG7QHMzMxB', // Mobile 320x180
'-oaymwESCOADEOgC8quKqQMG7QGZmRlC' // Mobile 480x360
];
class Deferred {
constructor() {
return Object.assign(
new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
}),
this);
}}
function isObject(obj) {
return obj !== null && typeof obj === 'object';
}
return results;
}
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;
}
window.addEventListener(pageLoadEventName, () => {
if (document.visibilityState === 'hidden') {
document.addEventListener('visibilitychange', ready, { once: true });
} else {
ready();
}
});
function ready() {
pageLoadedAndVisible.resolve();
pageLoadedAndVisible = new Deferred();
}
return native;
})();
function isUserLoggedIn() {
// Session Cookie exists?
if (!getSidCookie()) return false;
return false;
}
function getNext(payload) {
return sendInnertubeRequest('v1/next', payload, false);
}
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;
return parseInt(xmlhttp.responseText.match(/signatureTimestamp:([0-9]*)/)
[1]);
})());
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}`;
}
function info(msg) {
console.info(logPrefix, logPrefixStyle, 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}`;
}
}
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') || {};
wrappedPlayerResponse = isObject(playerResponse) ?
onInititalDataSet(playerResponse) : playerResponse;
if (typeof chainedPlayerSetter === 'function')
chainedPlayerSetter(wrappedPlayerResponse);
},
get: () => {
if (typeof chainedPlayerGetter === 'function')
try {
return chainedPlayerGetter();
} catch (err) {
// ignore the error
}
return wrappedPlayerResponse || {};
},
configurable: true });
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 getGoogleVideoUrl(originalUrl) {
return VIDEO_PROXY_SERVER_HOST + '/direct/' + btoa(originalUrl);
}
function getPlayer(payload) {
const queryParams = new URLSearchParams(payload).toString();
try {
const xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', proxyUrl, false);
xmlhttp.send(null);
return playerResponse;
} catch (err) {
error(err);
return { errorMessage: 'Proxy Connection failed' };
}
}
if (!isDesktop) {
nToast.nMessage = nToast.querySelector('.notification-action-response-text');
nToast.show = (message) => {
nToast.nMessage.innerText = message;
nToast.setAttribute('dir', 'in');
setTimeout(() => {
nToast.setAttribute('dir', 'out');
}, nToast.duration + 225);
};
}
await pageLoadedAndVisible;
const messagesMap = {
success: 'Age-restricted video successfully unlocked!',
fail: 'Unable to unlock this video 🙁 - More information in the developer
console' };
let lastProxiedGoogleVideoUrlParams;
let responseCache = {};
return [
// Strategy 1: Retrieve the video info by using a age-gate bypass for the
innertube API
// Source: https://github.com/yt-dlp/yt-dlp/issues/574#issuecomment-887171136
{
name: 'Embed',
requiresAuth: false,
payload: {
context: {
client: {
clientName,
clientVersion,
clientScreen: 'EMBED' },
thirdParty: {
embedUrl: 'https://www.youtube.com/' } },
playbackContext: {
contentPlaybackContext: {
signatureTimestamp } },
videoId },
getPlayer: getPlayer$1 },
playbackContext: {
contentPlaybackContext: {
signatureTimestamp } },
videoId },
getPlayer: getPlayer$1 },
// 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;
Toast.show(messagesMap.success);
}
let unlockedPlayerResponse;
unlockedPlayerResponse = strategy.getPlayer(strategy.payload,
strategy.requiresAuth);
return unlockedPlayerResponse;
}
function unlockNextResponse(originalNextResponse) {
info('Trying sidebar unlock');
videoId };
if (unlockedVideoSecondaryInfoRenderer.description)
originalVideoSecondaryInfoRenderer.description =
unlockedVideoSecondaryInfoRenderer.description;
return;
}
if (unlockedWatchNextFeed)
originalNextResponse.contents.singleColumnWatchNextResults.results.results.contents
.push(unlockedWatchNextFeed);
engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.ite
ms.find((x) => x.expandableVideoDescriptionBodyRenderer);
const unlockedStructuredDescriptionContentRenderer =
unlockedNextResponse.engagementPanels.
find((x) => x.engagementPanelSectionListRenderer).
engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.ite
ms.find((x) => x.expandableVideoDescriptionBodyRenderer);
if
(unlockedStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRendere
r)
originalStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer
=
unlockedStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer
;
}
function processThumbnails(responseObject) {
const thumbnails = findNestedObjectsByAttributeNames(responseObject, ['url',
'height']).filter((x) => typeof x.url === 'string' &&
x.url.indexOf('https://i.ytimg.com/') === 0);
const blurredThumbnails = thumbnails.filter((thumbnail) =>
THUMBNAIL_BLURRED_SQPS.some((sqp) => thumbnail.url.includes(sqp)));
try {
attachJsonInterceptor(checkAndUnlock);
attachXhrOpenInterceptor(onXhrOpenCalled);
attachInitialDataInterceptor(checkAndUnlock);
} 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');
}
try {
// Unlock blurry video thumbnails
if (isSearchResult(ytData) || isSearchResult(ytData.response)) {
processThumbnails(ytData);
}
} catch (err) {
error(err, 'Thumbnail unlock failed');
}
return ytData;
}
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...
return getGoogleVideoUrl(url.toString());
}
}
})(true);