Hacks Very Useful Informatiuon
Hacks Very Useful Informatiuon
/*
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(ranOnce) {
// Trick to get around the sandbox restrictions in Greasemonkey (Firefox)
// Inject code into the main window if criteria match
if (this !== window && !ranOnce) {
window.eval('(' + iife.toString() + ')(true);');
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://ny.4everproxy.com';
// Show notification?
let ENABLE_UNLOCK_NOTIFICATION = true;
/**
* The SQP parameter length is different for blurred thumbnails.
* They contain much less information, than normal thumbnails.
* The thumbnail SQPs tend to have a long and a short version.
*/
const BLURRED_THUMBNAIL_SQP_LENGTHS = [
32, // Mobile (SHORT)
48, // Desktop Playlist (SHORT)
56, // Desktop (SHORT)
68, // Mobile (LONG)
72, // Mobile Shorts
84, // Desktop Playlist (LONG)
88, // Desktop (LONG)
];
function isGoogleVideoUrl(url) {
return url.host.includes('.googlevideo.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 pageLoaded() {
if (document.readyState === 'complete') return Promise.resolve();
return deferred;
}
function createDeepCopy(obj) {
return nativeJSONParse(JSON.stringify(obj));
}
function getYtcfgValue(name) {
var _window$ytcfg;
return (_window$ytcfg = window.ytcfg) === null || _window$ytcfg === void
0 ? void 0 : _window$ytcfg.get(name);
}
function getSignatureTimestamp() {
return (
getYtcfgValue('STS')
|| ((_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 isUserLoggedIn() {
// LOGGED_IN doesn't exist on embedded page, use DELEGATED_SESSION_ID or
SESSION_INDEX as fallback
if (typeof getYtcfgValue('LOGGED_IN') === 'boolean') return
getYtcfgValue('LOGGED_IN');
if (typeof getYtcfgValue('DELEGATED_SESSION_ID') === 'string') return true;
if (parseInt(getYtcfgValue('SESSION_INDEX')) >= 0) return true;
return false;
}
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;
}
{
setTimeout(() => {
clearInterval(checkDomInterval);
deferred.reject();
}, timeout);
}
return deferred;
}
function isWatchNextObject(parsedData) {
var _parsedData$currentVi;
if (
!(parsedData !== null && parsedData !== void 0 && parsedData.contents)
|| !(parsedData !== null && parsedData !== void 0 &&
(_parsedData$currentVi = parsedData.currentVideoEndpoint) !== null &&
_parsedData$currentVi !== void 0
&& (_parsedData$currentVi = _parsedData$currentVi.watchEndpoint) !
== null && _parsedData$currentVi !== void 0 && _parsedData$currentVi.videoId)
) return false;
return !!parsedData.contents.twoColumnWatchNextResults || !!
parsedData.contents.singleColumnWatchNextResults;
}
function isWatchNextSidebarEmpty(parsedData) {
var _parsedData$contents2, _content$find;
if (isDesktop) {
var _parsedData$contents;
// WEB response layout
const result = (_parsedData$contents = parsedData.contents) === null ||
_parsedData$contents === void 0
|| (_parsedData$contents =
_parsedData$contents.twoColumnWatchNextResults) === null || _parsedData$contents
=== void 0
|| (_parsedData$contents =
_parsedData$contents.secondaryResults) === null || _parsedData$contents === void 0
|| (_parsedData$contents =
_parsedData$contents.secondaryResults) === null || _parsedData$contents === void 0
? void 0
: _parsedData$contents.results;
return !result;
}
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;
if (!(playabilityStatus !== null && playabilityStatus !== void 0 &&
playabilityStatus.status)) return false;
if (playabilityStatus.desktopLegacyAgeGateReason) return true;
if
(Config.UNLOCKABLE_PLAYABILITY_STATUSES.includes(playabilityStatus.status)) return
true;
function isSearchResult(parsedData) {
var _parsedData$contents3, _parsedData$contents4, _parsedData$onRespons;
return (
typeof (parsedData === null || parsedData === void 0 ||
(_parsedData$contents3 = parsedData.contents) === null || _parsedData$contents3 ===
void 0
? void 0
: _parsedData$contents3.twoColumnSearchResultsRenderer) ===
'object' // Desktop initial results
|| (parsedData === null || parsedData === void 0 ||
(_parsedData$contents4 = parsedData.contents) === null || _parsedData$contents4 ===
void 0
|| (_parsedData$contents4 =
_parsedData$contents4.sectionListRenderer) === null || _parsedData$contents4 ===
void 0
? void 0
: _parsedData$contents4.targetId) === 'search-feed' // Mobile
initial results
|| (parsedData === null || parsedData === void 0 ||
(_parsedData$onRespons = parsedData.onResponseReceivedCommands) === null ||
_parsedData$onRespons === void 0
|| (_parsedData$onRespons = _parsedData$onRespons.find((x)
=> x.appendContinuationItemsAction)) === null || _parsedData$onRespons === void 0
|| (_parsedData$onRespons =
_parsedData$onRespons.appendContinuationItemsAction) === null ||
_parsedData$onRespons === void 0
? void 0
: _parsedData$onRespons.targetId) === 'search-feed' // Desktop
& Mobile scroll continuation
);
}
obj[prop] = function() {
try {
onCall(arguments);
} catch {}
original.apply(this, arguments);
};
}
function info(msg) {
console.info(logPrefix, logPrefixStyle, msg);
if (window.SYARB_CONFIG) {
window.dispatchEvent(
new CustomEvent('SYARB_LOG_INFO', {
detail: {
message: 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}`;
}
}
/**
* 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.
*/
function attach$3(onInitialData) {
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;
});
// The global `ytInitialData` variable can be modified on the fly.
// It contains search results, sidebar data and meta information
// Not really important but fixes https://github.com/zerodytrash/Simple-
YouTube-Age-Restriction-Bypass/issues/127
window.addEventListener('DOMContentLoaded', () => {
if (isObject(window.ytInitialData)) {
onInitialData(window.ytInitialData);
}
});
}
function attach$1(onRequestCreate) {
if (typeof window.Request !== 'function') {
return;
}
if (modifiedUrl) {
args[0] = modifiedUrl;
}
}
}
} catch (err) {
error(err, `Failed to intercept Request()`);
}
function attach(onXhrOpenCalled) {
XMLHttpRequest.prototype.open = function(...args) {
let [method, url] = args;
try {
if (typeof url === 'string') {
if (url.indexOf('/') === 0) {
url = window.location.origin + url;
}
if (modifiedUrl) {
args[1] = modifiedUrl;
}
}
}
} catch (err) {
error(err, `Failed to intercept XMLHttpRequest.open()`);
}
nativeXMLHttpRequestOpen.apply(this, args);
};
}
function get(key) {
try {
return JSON.parse(localStorage.getItem(localStoragePrefix + key));
} catch {
return null;
}
}
xmlhttp.send(JSON.stringify(payload));
return nativeJSONParse(xmlhttp.responseText);
}
var innertube = {
getPlayer: getPlayer$1,
getNext: getNext$1,
};
function getGoogleVideoUrl(originalUrl) {
return Config.VIDEO_PROXY_SERVER_HOST + '/direct/' +
btoa(originalUrl.toString());
}
function getPlayer(payload) {
// Also request the /next response if a later /next request is likely.
if (!nextResponseCache[payload.videoId] && !isMusic && !isEmbed) {
payload.includeNext = 1;
}
function getNext(payload) {
// Next response already cached? => Return cached content
if (nextResponseCache[payload.videoId]) {
return nextResponseCache[payload.videoId];
}
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' };
}
}
var proxy = {
getPlayer,
getNext,
getGoogleVideoUrl,
};
return [
/**
* Retrieve the sidebar and video description by just adding
`racyCheckOk` and `contentCheckOk` params
* This strategy can be used to bypass content warnings
*/
{
name: 'Content Warning Bypass',
skip: !lastPlayerUnlockReason || !
lastPlayerUnlockReason.includes('CHECK_REQUIRED'),
optionalAuth: true,
payload: {
context: {
client: {
clientName,
clientVersion,
hl,
userInterfaceTheme,
},
},
videoId,
racyCheckOk: true,
contentCheckOk: true,
},
endpoint: innertube,
},
/**
* 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,
userInterfaceTheme,
isEmbed: +isEmbed,
isConfirmed: +isConfirmed,
},
endpoint: proxy,
},
];
}
return [
/**
* Retrieve the video info by just adding `racyCheckOk` and
`contentCheckOk` params
* This strategy can be used to bypass content warnings
*/
{
name: 'Content Warning Bypass',
skip: !reason || !reason.includes('CHECK_REQUIRED'),
optionalAuth: true,
payload: {
context: {
client: {
clientName: clientName,
clientVersion: clientVersion,
hl,
},
},
playbackContext: {
contentPlaybackContext: {
signatureTimestamp,
},
},
videoId,
startTimeSecs,
racyCheckOk: true,
contentCheckOk: true,
},
endpoint: innertube,
},
/**
* 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,
},
endpoint: innertube,
},
/**
* Retrieve the video info by using the WEB_CREATOR client in
combination with user authentication
* Requires that the user is logged in. Can bypass the tightened age
verification in the EU.
* See https://github.com/yt-dlp/yt-dlp/pull/600
*/
{
name: 'Creator + Auth',
requiresAuth: true,
payload: {
context: {
client: {
clientName: 'WEB_CREATOR',
clientVersion: '1.20210909.07.00',
hl,
},
},
playbackContext: {
contentPlaybackContext: {
signatureTimestamp,
},
},
videoId,
startTimeSecs,
racyCheckOk: true,
contentCheckOk: true,
},
endpoint: innertube,
},
/**
* Retrieve the video info 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,
reason,
clientName,
clientVersion,
signatureTimestamp,
startTimeSecs,
hl,
isEmbed: +isEmbed,
isConfirmed: +isConfirmed,
},
endpoint: proxy,
},
];
}
var buttonTemplate =
'<div style="margin-top: 15px !important; padding: 3px 10px 3px 10px;
margin: 0px auto; background-color: #4d4d4d; width: fit-content; font-size: 1.2em;
text-transform: uppercase; border-radius: 3px; cursor: pointer;">\n <div
class="button-text"></div>\n</div>';
buttons[id] = buttonElement;
errorScreenElement.append(buttonElement);
}
function removeButton(id) {
if (buttons[id] && buttons[id].isConnected) {
buttons[id].remove();
}
}
function isConfirmationRequired() {
return !isConfirmed && isEmbed && Config.ENABLE_UNLOCK_CONFIRMATION_EMBED;
}
function requestConfirmation() {
addButton(confirmationButtonId, confirmationButtonText, null, () => {
removeButton(confirmationButtonId);
confirm();
});
}
function confirm() {
setUrlParams({
unlock_confirmed: 1,
autoplay: 1,
});
}
var tMobile =
'<c3-toast>\n <ytm-notification-action-renderer>\n <div
class="notification-action-response-text"></div>\n </ytm-notification-action-
renderer>\n</c3-toast>\n';
await pageLoaded();
const messagesMap = {
success: 'Age-restricted video successfully unlocked!',
fail: 'Unable to unlock this video 🙁 - More information in the developer
console',
};
let lastProxiedGoogleVideoUrlParams;
let cachedPlayerResponse = {};
function getLastProxiedGoogleVideoId() {
var _lastProxiedGoogleVid;
return (_lastProxiedGoogleVid = lastProxiedGoogleVideoUrlParams) === null
|| _lastProxiedGoogleVid === void 0 ? void 0 : _lastProxiedGoogleVid.get('id');
}
function unlockResponse$1(playerResponse) {
var _playerResponse$video, _playerResponse$playa, _playerResponse$previ,
_unlockedPlayerRespon, _unlockedPlayerRespon3;
// Check if the user has to confirm the unlock first
if (isConfirmationRequired()) {
info('Unlock confirmation required.');
requestConfirmation();
return;
}
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);
lastPlayerUnlockVideoId = videoId;
lastPlayerUnlockReason = reason;
// 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.endpoint.getPlayer(strategy.payload, strategy.requiresAuth ||
strategy.optionalAuth);
} catch (err) {
error(err, `Player Unlock Method ${index + 1} failed with
exception`);
}
if (isStatusValid) {
var _unlockedPlayerRespon7;
/**
* Workaround: https://github.com/zerodytrash/Simple-YouTube-Age-
Restriction-Bypass/issues/191
*
* YouTube checks if the `trackingParams` in the response matches
the decoded `trackingParam` in `responseContext.mainAppWebResponseContext`.
* However, sometimes the response does not include the
`trackingParam` in the `responseContext`, causing the check to fail.
*
* This workaround addresses the issue by hardcoding the
`trackingParams` in the response context.
*/
if (
!unlockedPlayerResponse.trackingParams
|| !((_unlockedPlayerRespon7 =
unlockedPlayerResponse.responseContext) !== null && _unlockedPlayerRespon7 !== void
0
&& (_unlockedPlayerRespon7 =
_unlockedPlayerRespon7.mainAppWebResponseContext) !== null &&
_unlockedPlayerRespon7 !== void 0
&& _unlockedPlayerRespon7.trackingParam)
) {
unlockedPlayerResponse.trackingParams =
'CAAQu2kiEwjor8uHyOL_AhWOvd4KHavXCKw=';
unlockedPlayerResponse.responseContext = {
mainAppWebResponseContext: {
trackingParam:
'kx_fmPxhoPZRzgL8kzOwANUdQh8ZwHTREkw2UqmBAwpBYrzRgkuMsNLBwOcCE59TDtslLKPQ-SS',
},
};
}
/**
* Workaround: Account proxy response currently does not include
`playerConfig`
*
* Stays here until we rewrite the account proxy to only include
the necessary and bare minimum response
*/
if (strategy.payload.startTimeSecs && strategy.name === 'Account
Proxy') {
unlockedPlayerResponse.playerConfig = {
playbackStartConfig: {
startSeconds: strategy.payload.startTimeSecs,
},
};
}
}
return !isStatusValid;
});
return unlockedPlayerResponse;
}
function unlockResponse(originalNextResponse) {
const videoId =
originalNextResponse.currentVideoEndpoint.watchEndpoint.videoId;
if (!videoId) {
throw new Error(`Missing videoId in nextResponse`);
}
// Only unlock the /next response when the player has been unlocked as well
if (videoId !== lastPlayerUnlockVideoId) {
return;
}
function getUnlockedNextResponse(videoId) {
// Check if response is cached
if (cachedNextResponse.videoId === videoId) return
createDeepCopy(cachedNextResponse);
try {
unlockedNextResponse = strategy.endpoint.getNext(strategy.payload,
strategy.optionalAuth);
} catch (err) {
error(err, `Next Unlock Method ${index + 1} failed with
exception`);
}
return isWatchNextSidebarEmpty(unlockedNextResponse);
});
return unlockedNextResponse;
}
originalNextResponse.contents.twoColumnWatchNextResults.secondaryResults =
unlockedNextResponse.contents.twoColumnWatchNextResults.secondaryResults;
return;
}
if (unlockedWatchNextFeed)
originalNextResponse.contents.singleColumnWatchNextResults.results.results.contents
.push(unlockedWatchNextFeed);
if
(unlockedStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRendere
r) {
originalStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer
=
unlockedStructuredDescriptionContentRenderer.expandableVideoDescriptionBodyRenderer
;
}
}
/**
* Handles XMLHttpRequests and
* - Rewrite Googlevideo URLs to Proxy URLs (if necessary)
* - Store auth headers for the authentication of further unlock requests.
* - Add "content check ok" flags to request bodys
*/
function handleXhrOpen(method, url, xhr) {
const url_obj = new URL(url);
let proxyUrl = unlockGoogleVideo(url_obj);
if (proxyUrl) {
// Exclude credentials from XMLHttpRequest
Object.defineProperty(xhr, 'withCredentials', {
set: () => {},
get: () => false,
});
return proxyUrl.toString();
}
if (url_obj.pathname.indexOf('/youtubei/') === 0) {
// Store auth headers in storage for further usage.
attach$4(xhr, 'setRequestHeader', ([headerName, headerValue]) => {
if (Config.GOOGLE_AUTH_HEADER_NAMES.includes(headerName)) {
set(headerName, headerValue);
}
});
}
/**
* Handles Fetch requests and
* - Rewrite Googlevideo URLs to Proxy URLs (if necessary)
* - Store auth headers for the authentication of further unlock requests.
* - Add "content check ok" flags to request bodys
*/
function handleFetchRequest(url, requestOptions) {
const url_obj = new URL(url);
const newGoogleVideoUrl = unlockGoogleVideo(url_obj);
if (newGoogleVideoUrl) {
// Exclude credentials from Fetch Request
if (requestOptions.credentials) {
requestOptions.credentials = 'omit';
}
return newGoogleVideoUrl.toString();
}
/**
* 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...
* @returns The rewitten url (if a proxy is required)
*/
function unlockGoogleVideo(url) {
if (Config.VIDEO_PROXY_SERVER_HOST && isGoogleVideoUrl(url)) {
if (isGoogleVideoUnlockRequired(url, getLastProxiedGoogleVideoId())) {
return proxy.getGoogleVideoUrl(url);
}
}
}
/**
* Adds `contentCheckOk` and `racyCheckOk` to the given json data (if the data
contains a video id)
* @returns {string} The modified json
*/
function setContentCheckOk(bodyJson) {
try {
let parsedBody = JSON.parse(bodyJson);
if (parsedBody.videoId) {
parsedBody.contentCheckOk = true;
parsedBody.racyCheckOk = true;
return JSON.stringify(parsedBody);
}
} catch {}
return bodyJson;
}
function processThumbnails(responseObject) {
const thumbnails = findNestedObjectsByAttributeNames(responseObject,
['url', 'height']);
let blurredThumbnailCount = 0;
function isThumbnailBlurred(thumbnail) {
const hasSQPParam = thumbnail.url.indexOf('?sqp=') !== -1;
if (!hasSQPParam) {
return false;
}
return isBlurred;
}
try {
attach$3(processYtData);
attach$2(processYtData);
attach(handleXhrOpen);
attach$1(handleFetchRequest);
} 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)) {
unlockResponse$1(ytData);
} // Player Unlock #2: Embedded Player inital data structure
else if (isEmbeddedPlayerObject(ytData) &&
isAgeRestricted(ytData.previewPlayabilityStatus)) {
unlockResponse$1(ytData);
}
} catch (err) {
error(err, 'Video unlock failed');
}
try {
// Unlock sidebar watch next feed (sidebar) and video description
if (isWatchNextObject(ytData) && isWatchNextSidebarEmpty(ytData)) {
unlockResponse(ytData);
}
// Mobile version
if (isWatchNextObject(ytData.response) &&
isWatchNextSidebarEmpty(ytData.response)) {
unlockResponse(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;
}
})();