theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

Versione datata 17/01/2025. Vedi la nuova versione l'ultima versione.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==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     8.9
// @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((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);
            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 || !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';
        }
    });
})();