Simple YouTube Age Restriction Bypass

Watch age-restricted YouTube videos without login or age verification 😎

目前為 2024-12-18 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Simple YouTube Age Restriction Bypass
// @description  Watch age-restricted YouTube videos without login or age verification 😎
// @version      2.6.0
// @author       Zerody (Optimized by Cody)
// @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://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==

(function () {
    'use strict';

    // Configuration constants
    const UNLOCKABLE_PLAYABILITY_STATUSES = ['AGE_VERIFICATION_REQUIRED', 'AGE_CHECK_REQUIRED', 'CONTENT_CHECK_REQUIRED', 'LOGIN_REQUIRED'];
    const VALID_PLAYABILITY_STATUSES = ['OK', 'LIVE_STREAM_OFFLINE'];
    const ACCOUNT_PROXY_SERVER = 'https://youtube-proxy.zerody.one';
    const VIDEO_PROXY_SERVER = 'https://ny.4everproxy.com';
    const ENABLE_UNLOCK_NOTIFICATION = true;
    const ENABLE_UNLOCK_CONFIRMATION_EMBED = true;
    const GOOGLE_AUTH_HEADERS = ['Authorization', 'X-Goog-AuthUser', 'X-Origin'];
    const BLURRED_THUMBNAIL_LENGTHS = [32, 48, 56, 68, 72, 84, 88];
    const LOG_PREFIX = '%cYouTube Age Bypass:';
    const LOG_STYLE = 'color: white; background-color: #007BFF; padding: 3px;';

    const logger = {
        info: (msg) => console.info(LOG_PREFIX, LOG_STYLE, msg),
        error: (err, msg) => console.error(LOG_PREFIX, LOG_STYLE, msg, err),
    };

    // Helper to debounce functions
    const debounce = (fn, delay) => {
        let timeout;
        return (...args) => {
            clearTimeout(timeout);
            timeout = setTimeout(() => fn.apply(this, args), delay);
        };
    };

    // Element observer with timeout
    function waitForElement(selector, timeout = 5000) {
        return new Promise((resolve, reject) => {
            const observer = new MutationObserver((mutations, obs) => {
                const elem = document.querySelector(selector);
                if (elem) {
                    obs.disconnect();
                    resolve(elem);
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
            setTimeout(() => {
                observer.disconnect();
                reject(new Error('Element not found: ' + selector));
            }, timeout);
        });
    }

    // Check if video is restricted
    function isAgeRestricted(status) {
        return UNLOCKABLE_PLAYABILITY_STATUSES.includes(status?.status);
    }

    // Deep copy object
    function deepCopy(obj) {
        return JSON.parse(JSON.stringify(obj));
    }

    // Unlock video response
    function unlockResponse(response) {
        if (isAgeRestricted(response.playabilityStatus)) {
            logger.info('Unlocking video...');
            const unlockedResponse = fetchUnlockedResponse(response.videoDetails.videoId);
            if (unlockedResponse.errorMessage) {
                logger.error(null, `Unlock failed: ${unlockedResponse.errorMessage}`);
                return;
            }

            // Replace response content with unlocked data
            Object.assign(response, unlockedResponse);
            response.unlocked = true;
            logger.info('Video unlocked successfully.');
        }
    }

    // Fetch unlocked video response
    function fetchUnlockedResponse(videoId) {
        const payload = {
            context: {
                client: {
                    clientName: 'WEB',
                    clientVersion: '2.20220203.04.00',
                },
            },
            videoId,
            racyCheckOk: true,
            contentCheckOk: true,
        };

        try {
            const xhr = new XMLHttpRequest();
            xhr.open('POST', `${ACCOUNT_PROXY_SERVER}/youtubei/v1/player`, false);
            xhr.send(JSON.stringify(payload));
            return JSON.parse(xhr.responseText);
        } catch (err) {
            logger.error(err, 'Failed to fetch unlocked response');
            return { errorMessage: 'Unlock failed' };
        }
    }

    // Hook into JSON.parse to intercept video data
    const nativeJSONParse = JSON.parse;
    JSON.parse = function (text) {
        const data = nativeJSONParse.call(this, text);
        if (data && data.playabilityStatus) {
            try {
                unlockResponse(data);
            } catch (err) {
                logger.error(err, 'Error unlocking response');
            }
        }
        return data;
    };

    // Hook into XMLHttpRequest.open for unlocking
    const nativeXHROpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function (method, url, ...args) {
        if (url.includes('/youtubei/v1/player')) {
            this.addEventListener('readystatechange', function () {
                if (this.readyState === 4 && this.status === 200) {
                    try {
                        const response = JSON.parse(this.responseText);
                        unlockResponse(response);
                        this.responseText = JSON.stringify(response);
                    } catch (err) {
                        logger.error(err, 'Failed to intercept response');
                    }
                }
            });
        }
        nativeXHROpen.call(this, method, url, ...args);
    };

    logger.info('Script initialized. Age restriction bypass enabled.');
})();