diff --git a/plugins/bypass-age-restrictions/front.js b/plugins/bypass-age-restrictions/front.js index 1ec536f6..0f2d94d6 100644 --- a/plugins/bypass-age-restrictions/front.js +++ b/plugins/bypass-age-restrictions/front.js @@ -1,4 +1,1294 @@ module.exports = () => { - // See https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass#userscript - require("simple-youtube-age-restriction-bypass/dist/Simple-YouTube-Age-Restriction-Bypass.user.js"); + // ==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à 😎 + // @icon https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/raw/v2.5.4/src/extension/icon/icon_64.png + // @version 2.5.9 + // @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 + // @updateURL https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/raw/main/dist/Simple-YouTube-Age-Restriction-Bypass.user.js + // @license MIT + // @match https://www.youtube.com/* + // @match https://www.youtube-nocookie.com/* + // @match https://m.youtube.com/* + // @match https://music.youtube.com/* + // @grant none + // @run-at document-start + // @compatible chrome + // @compatible firefox + // @compatible opera + // @compatible edge + // @compatible safari + // ==/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(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; + } + + // Script configuration variables + const UNLOCKABLE_PLAYABILITY_STATUSES = ['AGE_VERIFICATION_REQUIRED', 'AGE_CHECK_REQUIRED', 'CONTENT_CHECK_REQUIRED', 'LOGIN_REQUIRED']; + const VALID_PLAYABILITY_STATUSES = ['OK', 'LIVE_STREAM_OFFLINE']; + + // 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'; + + // User needs to confirm the unlock process on embedded player? + let ENABLE_UNLOCK_CONFIRMATION_EMBED = true; + + // Show notification? + let ENABLE_UNLOCK_NOTIFICATION = true; + + // Disable content warnings? + let SKIP_CONTENT_WARNINGS = true; + + // Some Innertube bypass methods require the following authentication headers of the currently logged in user. + const GOOGLE_AUTH_HEADER_NAMES = ['Authorization', 'X-Goog-AuthUser', 'X-Origin']; + + /** + * 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) + ]; + + // small hack to prevent tree shaking on these exports + var Config = window[Symbol()] = { + UNLOCKABLE_PLAYABILITY_STATUSES, + VALID_PLAYABILITY_STATUSES, + ACCOUNT_PROXY_SERVER_HOST, + VIDEO_PROXY_SERVER_HOST, + ENABLE_UNLOCK_CONFIRMATION_EMBED, + ENABLE_UNLOCK_NOTIFICATION, + SKIP_CONTENT_WARNINGS, + GOOGLE_AUTH_HEADER_NAMES, + BLURRED_THUMBNAIL_SQP_LENGTHS, + }; + + function isGoogleVideoUrl(url) { + return 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; + } + + const nativeJSONParse = window.JSON.parse; + const nativeXMLHttpRequestOpen = window.XMLHttpRequest.prototype.open; + + const isDesktop = window.location.host !== 'm.youtube.com'; + const isMusic = window.location.host === 'music.youtube.com'; + const isEmbed = window.location.pathname.indexOf('/embed/') === 0; + const isConfirmed = window.location.search.includes('unlock_confirmed'); + + 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 findNestedObjectsByAttributeNames(object, attributeNames) { + var results = []; + + // Does the current object match the attribute conditions? + if (attributeNames.every((key) => typeof object[key] !== 'undefined')) { + results.push(object); + } + + // Diggin' deeper for each nested object (recursive) + Object.keys(object).forEach((key) => { + if (object[key] && typeof object[key] === 'object') { + results.push(...findNestedObjectsByAttributeNames(object[key], attributeNames)); + } + }); + + return results; + } + + function pageLoaded() { + if (document.readyState === 'complete') return Promise.resolve(); + + const deferred = new Deferred(); + + window.addEventListener('load', deferred.resolve, { once: true }); + + 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') + || (() => { + 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 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', ''); + + if (startTimeString && !isNaN(startTimeString)) { + return parseInt(startTimeString); + } + } + + 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; + } + + function waitForElement(elementSelector, timeout) { + const deferred = new Deferred(); + + const checkDomInterval = setInterval(() => { + const elem = document.querySelector(elementSelector); + if (elem) { + clearInterval(checkDomInterval); + deferred.resolve(elem); + } + }, 100); + + if (timeout) { + setTimeout(() => { + clearInterval(checkDomInterval); + deferred.reject(); + }, timeout); + } + + return deferred; + } + + function parseRelativeUrl(url) { + if (typeof url !== 'string') { + return null; + } + + if (url.indexOf('/') === 0) { + url = window.location.origin + url; + } + + try { + return url.indexOf('https://') === 0 ? new window.URL(url) : null; + } catch { + return null; + } + } + + 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; + } + + // MWEB response layout + const content = (_parsedData$contents2 = parsedData.contents) === null || _parsedData$contents2 === void 0 + || (_parsedData$contents2 = _parsedData$contents2.singleColumnWatchNextResults) === null || _parsedData$contents2 === void 0 + || (_parsedData$contents2 = _parsedData$contents2.results) === null || _parsedData$contents2 === void 0 + || (_parsedData$contents2 = _parsedData$contents2.results) === null || _parsedData$contents2 === void 0 + ? void 0 + : _parsedData$contents2.contents; + const result = content === null || content === 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 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; + + // 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 + || (_playabilityStatus$er = _playabilityStatus$er.playerErrorMessageRenderer) === null || _playabilityStatus$er === void 0 + || (_playabilityStatus$er = _playabilityStatus$er.reason) === null || _playabilityStatus$er === void 0 + || (_playabilityStatus$er = _playabilityStatus$er.runs) === null || _playabilityStatus$er === void 0 + || (_playabilityStatus$er = _playabilityStatus$er.find((x) => x.navigationEndpoint)) === null || _playabilityStatus$er === void 0 + || (_playabilityStatus$er = _playabilityStatus$er.navigationEndpoint) === null || _playabilityStatus$er === void 0 + || (_playabilityStatus$er = _playabilityStatus$er.urlEndpoint) === null || _playabilityStatus$er === void 0 + || (_playabilityStatus$er = _playabilityStatus$er.url) === null || _playabilityStatus$er === void 0 + ? void 0 + : _playabilityStatus$er.includes('/2802167')) + ); + } + + 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 + ); + } + + function attach$3(obj, prop, onCall) { + if (!obj || typeof obj[prop] !== 'function') { + return; + } + + let original = obj[prop]; + + obj[prop] = function() { + try { + onCall(arguments); + } catch {} + original.apply(this, arguments); + }; + } + + // Intercept, inspect and modify JSON-based communication to unlock player responses by hijacking the JSON.parse function + function attach$2(onJsonDataReceived) { + window.JSON.parse = function() { + const data = nativeJSONParse.apply(this, arguments); + return isObject(data) ? onJsonDataReceived(data) : data; + }; + } + + const logPrefix = '%cSimple-YouTube-Age-Restriction-Bypass:'; + const logPrefixStyle = 'background-color: #1e5c85; color: #fff; font-size: 1.2em;'; + const logSuffix = '\uD83D\uDC1E You can report bugs at: https://github.com/zerodytrash/Simple-YouTube-Age-Restriction-Bypass/issues'; + + function error(err, msg) { + console.error(logPrefix, logPrefixStyle, msg, err, getYtcfgDebugString(), '\n\n', logSuffix); + if (window.SYARB_CONFIG) { + window.dispatchEvent( + new CustomEvent('SYARB_LOG_ERROR', { + detail: { + message: (msg ? msg + '; ' : '') + (err && err.message ? err.message : ''), + stack: err && err.stack ? err.stack : null, + }, + }), + ); + } + } + + 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}`; + } + } + + function attach$1(onRequestCreate) { + if (typeof window.Request !== 'function') { + return; + } + + window.Request = new Proxy(window.Request, { + construct(target, args) { + const [url, options] = args; + try { + const parsedUrl = parseRelativeUrl(url); + const modifiedUrl = onRequestCreate(parsedUrl, options); + + if (modifiedUrl) { + args[0] = modifiedUrl.toString(); + } + } catch (err) { + error(err, `Failed to intercept Request()`); + } + + return Reflect.construct(...arguments); + }, + }); + } + + function attach(onXhrOpenCalled) { + XMLHttpRequest.prototype.open = function(method, url) { + try { + let parsedUrl = parseRelativeUrl(url); + + if (parsedUrl) { + const modifiedUrl = onXhrOpenCalled(method, parsedUrl, this); + + if (modifiedUrl) { + arguments[1] = modifiedUrl.toString(); + } + } + } catch (err) { + error(err, `Failed to intercept XMLHttpRequest.open()`); + } + + nativeXMLHttpRequestOpen.apply(this, arguments); + }; + } + + const localStoragePrefix = 'SYARB_'; + + function set(key, value) { + localStorage.setItem(localStoragePrefix + key, JSON.stringify(value)); + } + + function get(key) { + try { + return JSON.parse(localStorage.getItem(localStoragePrefix + key)); + } catch { + return null; + } + } + + function getPlayer$1(payload, useAuth) { + return sendInnertubeRequest('v1/player', payload, useAuth); + } + + function getNext$1(payload, useAuth) { + return sendInnertubeRequest('v1/next', payload, useAuth); + } + + function sendInnertubeRequest(endpoint, payload, useAuth) { + const xmlhttp = new XMLHttpRequest(); + xmlhttp.open('POST', `/youtubei/${endpoint}?key=${getYtcfgValue('INNERTUBE_API_KEY')}&prettyPrint=false`, false); + + if (useAuth && isUserLoggedIn()) { + xmlhttp.withCredentials = true; + Config.GOOGLE_AUTH_HEADER_NAMES.forEach((headerName) => { + xmlhttp.setRequestHeader(headerName, get(headerName)); + }); + } + + xmlhttp.send(JSON.stringify(payload)); + return nativeJSONParse(xmlhttp.responseText); + } + + var innertube = { + getPlayer: getPlayer$1, + getNext: getNext$1, + }; + + let nextResponseCache = {}; + + 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; + } + + return sendRequest('getPlayer', payload); + } + + function getNext(payload) { + // Next response already cached? => Return cached content + if (nextResponseCache[payload.videoId]) { + return nextResponseCache[payload.videoId]; + } + + return sendRequest('getNext', payload); + } + + function sendRequest(endpoint, payload) { + const queryParams = new URLSearchParams(payload); + const proxyUrl = `${Config.ACCOUNT_PROXY_SERVER_HOST}/${endpoint}?${queryParams}&client=js`; + + try { + const xmlhttp = new XMLHttpRequest(); + xmlhttp.open('GET', proxyUrl, false); + xmlhttp.send(null); + + const proxyResponse = nativeJSONParse(xmlhttp.responseText); + + // Mark request as 'proxied' + proxyResponse.proxied = true; + + // Put included /next response in the cache + if (proxyResponse.nextResponse) { + nextResponseCache[payload.videoId] = proxyResponse.nextResponse; + delete proxyResponse.nextResponse; + } + + return proxyResponse; + } catch (err) { + error(err, 'Proxy API Error'); + return { errorMessage: 'Proxy Connection failed' }; + } + } + + var proxy = { + getPlayer, + getNext, + getGoogleVideoUrl, + }; + + function getUnlockStrategies$1(videoId, lastPlayerUnlockReason) { + var _getYtcfgValue$client; + const clientName = getYtcfgValue('INNERTUBE_CLIENT_NAME') || 'WEB'; + const clientVersion = getYtcfgValue('INNERTUBE_CLIENT_VERSION') || '2.20220203.04.00'; + const hl = getYtcfgValue('HL'); + const userInterfaceTheme = (_getYtcfgValue$client = getYtcfgValue('INNERTUBE_CONTEXT').client.userInterfaceTheme) !== null && _getYtcfgValue$client !== void 0 + ? _getYtcfgValue$client + : document.documentElement.hasAttribute('dark') + ? 'USER_INTERFACE_THEME_DARK' + : 'USER_INTERFACE_THEME_LIGHT'; + + 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, + }, + ]; + } + + function getUnlockStrategies(videoId, reason) { + const clientName = getYtcfgValue('INNERTUBE_CLIENT_NAME') || 'WEB'; + const clientVersion = getYtcfgValue('INNERTUBE_CLIENT_VERSION') || '2.20220203.04.00'; + const signatureTimestamp = getSignatureTimestamp(); + const startTimeSecs = getCurrentVideoStartTime(videoId); + const hl = getYtcfgValue('HL'); + + 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 = + '