theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

As of 2025-01-17. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        theYNC.com Underground bypass
// @description Watch theYNC Underground videos without needing an account
// @namespace   Violentmonkey Scripts
// @match       *://*.theync.com/*
// @match       *://theync.com/*
// @match       *://*.theync.net/*
// @match       *://theync.net/*
// @match       *://*.theync.org/*
// @match       *://theync.org/*
// @match       *://archive.ph/*
// @match       *://archive.today/*
// @include     /https?:\/\/web\.archive\.org\/web\/\d+?\/https?:\/\/theync\.(?:com|org|net)/
// @require     https://update.greasyfork.org/scripts/523012/1519437/WaitForKeyElement.js
// @grant       GM.xmlHttpRequest
// @connect     media.theync.com
// @connect     archive.org
// @grant       GM_addStyle
// @grant       GM_log
// @grant       GM_addElement
// @version     9.1
// @supportURL  https://greasyfork.org/en/scripts/520352-theync-com-underground-bypass/feedback
// @license     MIT
// @author      https://greasyfork.org/en/users/1409235-paywalldespiser
// ==/UserScript==

/**
 * Fetches available archives of a given address and retrieves their URLs.
 *
 * @param {string} address
 * @returns {Promise<string>}
 */
function queryArchive(address) {
    try {
        const url = new URL('https://archive.org/wayback/available');
        url.searchParams.append('url', address);

        return GM.xmlHttpRequest({
            method: 'GET',
            url,
            redirect: 'follow',
            responseType: 'json',
        })
            .then((result) => {
                if (result.status >= 300) {
                    console.error(result.status);
                    return Promise.reject(result);
                }

                return result;
            })
            .then((result) => result.response)
            .then((result) => {
                if (
                    result.archived_snapshots &&
                    result.archived_snapshots.closest
                ) {
                    return result.archived_snapshots.closest.url;
                }
                return Promise.reject();
            });
    } catch (e) {
        return Promise.reject();
    }
}

/**
 * Checks whether a URL is valid and accessible.
 *
 * @param {string?} address
 * @returns {Promise<string>}
 */
function isValidURL(address) {
    if (!address) {
        return Promise.reject(address);
    }
    try {
        const url = new URL(address);
        return GM.xmlHttpRequest({ url, method: 'HEAD' }).then((result) => {
            if (result.status === 404) {
                return Promise.reject(address);
            }
            return address;
        });
    } catch {
        return Promise.reject(address);
    }
}

/**
 * Tries to guess the video URL of a given theYNC video via the thumbnail URL.
 * Only works on videos published before around May 2023.
 *
 * @param {Element} element
 * @returns {string?}
 */
function getTheYNCVideoURL(element) {
    /**
     * @type {string | undefined | null}
     */
    const thumbnailURL = element.querySelector('.image > img')?.src;
    if (thumbnailURL) {
        for (const [, group_url] of thumbnailURL.matchAll(
            /^https?:\/\/(?:media\.theync\.(?:com|org|net)|(www\.)?theync\.(?:com|org|net)\/media)\/thumbs\/(.+?)\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v)/gim
        )) {
            if (group_url) {
                return 'https://media.theync.com/videos/' + group_url + '.mp4';
            }
        }
    }

    return null;
}

/**
 * Retrieves the video URL from a theYNC video page
 *
 * @param {Element} [element=document]
 * @returns {string?}
 */
function retrieveVideoURL(element = document) {
    if (location.host === 'archive.ph' || location.host === 'archive.today') {
        const attribute = element
            .querySelector('[id="thisPlayer"] video[old-src]')
            ?.getAttribute('old-src');
        if (attribute) {
            return attribute;
        }
    }
    /**
     * @type {string | null | undefined}
     */
    const videoSrc = element.querySelector(
        '.stage-video > .inner-stage video[src]'
    )?.src;
    if (videoSrc) {
        return videoSrc;
    }
    const playerSetupScript = element.querySelector(
        '[id=thisPlayer] + script'
    )?.textContent;
    if (playerSetupScript) {
        // TODO: Find a non-regex solution to this that doesn't involve eval
        for (const [, videoURL] of playerSetupScript.matchAll(
            /(?<=file\:) *?"(?:https?:\/\/web.archive.org\/web\/\d+?\/)?(https?:\/\/(?:(?:www\.)?theync\.(?:com|org|net)\/media|media.theync\.(?:com|org|net))\/videos\/.+?\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v))"/gim
        )) {
            if (videoURL) {
                return decodeURIComponent(videoURL);
            }
        }
    }
    return null;
}

/**
 * Retrieves the video URL from an archived YNC URL
 *
 * @param {string} archiveURL
 * @returns {Promise<string>}
 */
function getVideoURLFromArchive(archiveURL) {
    return GM.xmlHttpRequest({ url: archiveURL, method: 'GET' })
        .then((result) => {
            if (result.status >= 300) {
                console.error(result.status);
                return Promise.reject(result);
            }
            return result;
        })

        .then((result) => {
            // Initialize the DOM parser
            const parser = new DOMParser();

            // Parse the text
            const doc = parser.parseFromString(
                result.responseText,
                'text/html'
            );

            // You can now even select part of that html as you would in the regular DOM
            // Example:
            // const docArticle = doc.querySelector('article').innerHTML
            const videoURL = retrieveVideoURL(doc);
            if (videoURL) {
                return videoURL;
            }
            return Promise.reject();
        });
}

(function () {
    'use strict';

    const allowedExtensions = [
        'flv',
        'mpg',
        'wmv',
        'avi',
        '3gp',
        'qt',
        'mp4',
        'mov',
        'm4v',
        'f4v',
    ];

    GM_addStyle(`
            .loader {
                border: 0.25em solid #f3f3f3;
                border-top: 0.25em solid rgba(0, 0, 0, 0);
                border-radius: 50%;
                width: 1em;
                height: 1em;
                animation: spin 2s linear infinite;
            }
            
            @keyframes spin {
                0% {
                    transform: rotate(0deg);
                }
            
                100% {
                    transform: rotate(360deg);
                }
            }
            
            .border-gold {
                display: flex !important;
                align-items: center;
                justify-content: center;
                gap: 1em;
            }
            `);

    waitForKeyElement(
        '[id="content"],[id="related-videos"] .content-block'
    ).then(async (contentBlock) => {
        for (const element of contentBlock.querySelectorAll(
            '.upgrade-profile > .upgrade-info-block > .image-block'
        )) {
            isValidURL(getTheYNCVideoURL(element)).then(
                (url) => (location.href = url)
            );
        }
        for (/** @type {HTMLLinkElement} */ const element of contentBlock.querySelectorAll(
            '.inner-block > a'
        )) {
            const undergroundLogo = element.querySelector(
                '.item-info > .border-gold'
            );
            if (!undergroundLogo) {
                continue;
            }
            const loadingElement = GM_addElement('div');
            loadingElement.classList.add('loader');
            undergroundLogo.appendChild(loadingElement);
            await isValidURL(getTheYNCVideoURL(element))
                .then(
                    (url) => ({
                        url: url,
                        text: 'BYPASSED',
                        color: 'green',
                    }),

                    () => {
                        /**
                         * @type {RegExpMatchArray | null}
                         */
                        const match = element.href.match(
                            /(^https?:\/\/(?:www\.)?theync\.)(?:com|org|net)(\/.*$)/im
                        );
                        if (!match?.[1]) {
                            return Promise.reject(
                                'Error with the URL: ' + element.href
                            );
                        }
                        const [, secondLevelDomain, path] = match;

                        return ['com', 'org', 'net']
                            .reduce(
                                /**
                                 * @param {Promise<string>} accumulator
                                 * @param {string} currentTLD
                                 * @returns {Promise<string>}
                                 */
                                (accumulator, currentTLD) =>
                                    accumulator.catch(() =>
                                        queryArchive(
                                            secondLevelDomain +
                                                currentTLD +
                                                path
                                        )
                                    ),
                                Promise.reject()
                            )
                            .then((archiveURL) =>
                                getVideoURLFromArchive(archiveURL).then(
                                    (videoURL) => ({
                                        url: videoURL,
                                        text: 'ARCHIVED',
                                        color: 'blue',
                                    }),
                                    () => ({
                                        url: archiveURL,
                                        text: 'MAYBE ARCHIVED',
                                        color: 'aqua',
                                    })
                                )
                            );
                    }
                )
                .catch(() => ({
                    url:
                        'https://archive.ph/' +
                        encodeURIComponent(element.href),
                    text: 'Try archive.today',
                    color: 'red',
                }))
                .then(({ url, text, color }) => {
                    undergroundLogo.textContent = text;
                    undergroundLogo.style.backgroundColor = color;
                    element.href = url;
                })
                .finally(() => loadingElement.remove());
        }
    });
    waitForKeyElement('[id="stage"]:has([id="thisPlayer"])').then((stage) => {
        const videoURL = retrieveVideoURL();
        if (videoURL) {
            stage.innerHTML = '';
            stage.style.textAlign = 'center';

            const video = GM_addElement(stage, 'video', {
                controls: 'controls',
            });
            video.style.width = 'auto';
            video.style.height = '100%';
            const source = GM_addElement(video, 'source');
            source.src = videoURL;
            source.type = 'video/mp4';
        }
    });
})();