Untitled
Untitled
Untitled
/*
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 (this !== window && inject) {
window.eval("(" + iife.toString() + ")();");
return;
}
// These are the proxy servers that are sometimes required to unlock videos with
age restrictions.
// You can host your own account proxy instance. See
https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/tree/main/
account-proxy
// To learn what information is transferred, please read:
https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass#privacy
const ACCOUNT_PROXY_SERVER_HOST = 'https://youtube-proxy.zerody.one';
const VIDEO_PROXY_SERVER_HOST = 'https://phx.4everproxy.com';
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();
}
function pageLoaded() {
if (document.readyState === 'complete') return Promise.resolve();
return deferred;
}
function createDeepCopy(obj) {
return nativeJSONParse(JSON.stringify(obj));
}
function getCurrentVideoStartTime(currentVideoId) {
// Check if the URL corresponds to the requested video
// This is not the case when the player gets preloaded for the next video in a
playlist.
if (window.location.href.includes(currentVideoId)) {var _ref;
// "t"-param on youtu.be urls
// "start"-param on embed player
// "time_continue" when clicking "watch on youtube" on embedded player
const urlParams = new URLSearchParams(window.location.search);
const startTimeString = (_ref = urlParams.get('t') || urlParams.get('start')
|| urlParams.get('time_continue')) === null || _ref === void 0 ? void 0 :
_ref.replace('s', '');
return 0;
}
function setUrlParams(params) {
const urlParams = new URLSearchParams(window.location.search);
for (const paramName in params) {
urlParams.set(paramName, params[paramName]);
}
window.location.search = urlParams;
}
if (timeout) {
setTimeout(() => {
clearInterval(checkDomInterval);
deferred.reject();
}, timeout);
}
return deferred;
}
// 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 (var 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 (var 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 (var blockstart = 0; blockstart < word_array.length; blockstart += 16) {
for (var i = 0; i < 16; i++) W[i] = word_array[blockstart + i];
for (var 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 (var 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 (var 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 (var 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 (var 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;
}
function isUserLoggedIn() {
// Session Cookie exists?
if (!getSidCookie()) return false;
return 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}`;
}
}
function attachInitialDataInterceptor(onInitialData) {
// And here we deal with YouTube's crappy initial data (present in page source)
and the problems that occur when intercepting that data.
// YouTube has some protections in place that make it difficult to intercept
and modify the global ytInitialPlayerResponse variable.
// The easiest way would be to set a descriptor on that variable to change the
value directly on declaration.
// But some adblockers define their own descriptors on the
ytInitialPlayerResponse variable, which makes it hard to register another
descriptor on it.
// As a workaround only the relevant playerResponse property of the
ytInitialPlayerResponse variable will be intercepted.
// This is achieved by defining a descriptor on the object prototype for that
property, which affects any object with a `playerResponse` property.
interceptObjectProperty('playerResponse', (obj, playerResponse) => {
info(`playerResponse property set, contains sidebar: ${!!obj.response}`);
// The same object also contains the sidebar data and video description
if (isObject(obj.response)) onInitialData(obj.response);
// If the script is executed too late and the bootstrap data has already been
processed,
// a reload of the player can be forced by creating a deep copy of the
object.
// This is especially relevant if the userscript manager does not handle the
`@run-at document-start` correctly.
playerResponse.unlocked = false;
onInitialData(playerResponse);
return playerResponse.unlocked ? createDeepCopy(playerResponse) :
playerResponse;
});
function attachXhrOpenInterceptor(onXhrOpenCalled) {
XMLHttpRequest.prototype.open = function (method, url) {
if (typeof url === 'string' && url.indexOf('https://') === 0) {
const modifiedUrl = onXhrOpenCalled(this, method, new URL(url));
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_PLAYABILITY_STATUSES.includes(playabilityStatus.status)) return
true;
function getGoogleVideoUrl(originalUrl) {
return VIDEO_PROXY_SERVER_HOST + '/direct/' + btoa(originalUrl);
}
try {
const xmlhttp = new XMLHttpRequest();
xmlhttp.open('GET', proxyUrl, false);
xmlhttp.send(null);
return proxyResponse;
} catch (err) {
error(err, 'Proxy API Error');
return { errorMessage: 'Proxy Connection failed' };
}
}
function getPlayer(payload) {
return sendRequest('getPlayer', payload);
}
function getNext(payload) {
return sendRequest('getNext', payload);
}
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 pageLoaded();
if (backgroundColor) {
buttonElement.querySelector(':scope > div').style['background-color'] =
backgroundColor;
}
buttons[id] = buttonElement;
errorScreenElement.append(buttonElement);
}
function removeButton(id) {
if (buttons[id] && buttons[id].isConnected) {
buttons[id].remove();
}
}
function isConfirmationRequired() {
return !isConfirmed && isEmbed && ENABLE_UNLOCK_CONFIRMATION_EMBED;
}
function requestConfirmation() {
addButton(confirmationButtonId, confirmationButtonText, null, () => {
removeButton(confirmationButtonId);
confirm();
});
}
function confirm() {
setUrlParams({
unlock_confirmed: 1,
autoplay: 1 });
const messagesMap = {
success: 'Age-restricted video successfully unlocked!',
fail: 'Unable to unlock this video 🙁 - More information in the developer
console' };
let lastProxiedGoogleVideoUrlParams;
let cachedPlayerResponse = {};
return [
// Strategy 1: Retrieve the video info by using the TVHTML5 Embedded client
// This client has no age restrictions in place (2022-03-28)
// See https://github.com/zerodytrash/YouTube-Internal-Clients
{
name: 'TV Embedded Player',
requiresAuth: false,
payload: {
context: {
client: {
clientName: 'TVHTML5_SIMPLY_EMBEDDED_PLAYER',
clientVersion: '2.0',
clientScreen: 'WATCH',
hl },
thirdParty: {
embedUrl: 'https://www.youtube.com/' } },
playbackContext: {
contentPlaybackContext: {
signatureTimestamp } },
videoId,
startTimeSecs,
racyCheckOk: true,
contentCheckOk: true },
getPlayer: getPlayer$1 },
playbackContext: {
contentPlaybackContext: {
signatureTimestamp } },
videoId,
startTimeSecs,
racyCheckOk: true,
contentCheckOk: true },
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;
playerResponse.unlocked = true;
Toast.show(messagesMap.success);
}
try {
unlockedPlayerResponse = strategy.getPlayer(strategy.payload,
strategy.requiresAuth);
} catch (err) {
error(err, `Player Unlock Method ${index + 1} failed with exception`);
}
return !VALID_PLAYABILITY_STATUSES.includes((_unlockedPlayerRespon6 =
unlockedPlayerResponse) === null || _unlockedPlayerRespon6 === void 0 ? void 0 :
(_unlockedPlayerRespon7 = _unlockedPlayerRespon6.playabilityStatus) === null ||
_unlockedPlayerRespon7 === void 0 ? void 0 : _unlockedPlayerRespon7.status);
});
return unlockedPlayerResponse;
}
return [
// Strategy 1: Retrieve the sidebar and video description from an account proxy
server.
// Session cookies of an age-verified Google account are stored on server side.
// See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/
tree/main/account-proxy
{
name: 'Account Proxy',
payload: {
videoId,
clientName,
clientVersion,
hl,
isEmbed: +isEmbed,
isConfirmed: +isConfirmed },
function unlockNextResponse(originalNextResponse) {
const unlockedNextResponse = getUnlockedNextResponse(originalNextResponse);
function getUnlockedNextResponse(nextResponse) {
const videoId = nextResponse.currentVideoEndpoint.watchEndpoint.videoId;
if (!videoId) {
throw new Error(`Missing videoId in nextResponse`);
}
return isWatchNextSidebarEmpty(unlockedNextResponse);
});
return unlockedNextResponse;
}
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 {
attachInitialDataInterceptor(processYtData);
attachJsonInterceptor(processYtData);
attachXhrOpenInterceptor(onXhrOpenCalled);
} catch (err) {
error(err, 'Error while attaching data interceptors');
}
function processYtData(ytData) {
try {
// Player Unlock #1: Initial page data structure and response from
`/youtubei/v1/player` XHR request
if (isPlayerObject(ytData) && isAgeRestricted(ytData.playabilityStatus)) {
unlockPlayerResponse(ytData);
}
// Player Unlock #2: Embedded Player inital data structure
else if (isEmbeddedPlayerObject(ytData) &&
isAgeRestricted(ytData.previewPlayabilityStatus)) {
unlockPlayerResponse(ytData);
}
} catch (err) {
error(err, 'Video unlock failed');
}
try {
// Unlock sidebar watch next feed (sidebar) and video description
if (isWatchNextObject(ytData) && isWatchNextSidebarEmpty(ytData)) {
unlockNextResponse(ytData);
}
// Mobile version
if (isWatchNextObject(ytData.response) &&
isWatchNextSidebarEmpty(ytData.response)) {
unlockNextResponse(ytData.response);
}
} catch (err) {
error(err, 'Sidebar unlock failed');
}
try {
// Unlock blurry video thumbnails in search results
if (isSearchResult(ytData)) {
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);