theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

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

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

You will need to install an extension such as Tampermonkey to install this 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
// @require     https://cdn.jsdelivr.net/npm/@trim21/[email protected]
// @namespace   Violentmonkey Scripts
// @match       *://*.theync.com/*
// @match       *://theync.com/*
// @match       *://*.theync.net/*
// @match       *://theync.net/*
// @match       *://*.theync.org/*
// @match       *://theync.org/*
// @grant       GM.xmlHttpRequest
// @connect     media.theync.com
// @connect     archive.org
// @grant       GM_addStyle
// @grant       GM_log
// @version     6.3
// @supportURL  https://greasyfork.org/en/scripts/520352-theync-com-underground-bypass/feedback
// @license     MIT
// @author      -
// ==/UserScript==

/**
 * Waits for a element of a given selector.
 *
 * @param {string} selector
 * @returns {Promise<Element>}
 */
function waitForElement(selector) {
    return new Promise((resolve) => {
        {
            const element = document.querySelector(selector);
            if (element) {
                return resolve(element);
            }
        }

        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                if (mutation.type !== 'childList' || !mutation.addedNodes) {
                    continue;
                }
                for (const addedNode of mutation.addedNodes) {
                    if (addedNode.nodeType === Node.ELEMENT_NODE) {
                        if (addedNode.matches(selector)) {
                            observer.disconnect();
                            return resolve(element);
                        }
                        const childElement = addedNode.querySelector(selector);
                        if (childElement) {
                            observer.disconnect();
                            return resolve(childElement);
                        }
                    }
                }
            }
        });

        // If you get 'parameter 1 is not of type 'Node'' error, see https://stackoverflow.com/a/77855838/492336
        observer.observe(document.body, {
            childList: true,
            subtree: true,
        });
    });
}

/**
 * Fetches available archives of a given address.
 *
 * @param {string} address
 * @returns {Promise<Response>}
 */
function fetchArchive(address) {
    try {
        const url = new URL('https://archive.org/wayback/available');
        url.searchParams.append('url', address);
        return GM_fetch(url, {
            method: 'GET',
        });
    } catch (e) {
        return Promise.reject();
    }
}

/**
 * Fetches available archives of a given address and retrieves their URLs.
 *
 * @param {string} address
 * @returns {Promise<string>}
 */
function queryArchive(address) {
    return fetchArchive(address)
        .then((archiveResponse) => {
            if (!archiveResponse.ok) {
                console.error(archiveResponse);
                return Promise.reject(archiveResponse);
            }
            return archiveResponse;
        })
        .then((archiveResponse) => archiveResponse.json())
        .then(({ archived_snapshots }) => {
            if (archived_snapshots.closest) {
                return archived_snapshots.closest.url;
            }
            return Promise.reject(archived_snapshots.closest?.url);
        })
        .then((url) => {
            // Avoid "Mixed content"
            if (location.protocol === 'https:') {
                return url.replace(/^http:\/\//i, 'https://');
            }
            return url;
        });
}

/**
 * Gets the comments given a video id
 *
 * @param {number} id
 * @returns {Promise<string>}
 */
function getComments(id) {
    const url = new URL(
        'https://theync.com/templates/theync/template.ajax_comments.php'
    );
    url.searchParams.append('id', id);
    url.searchParams.append('time', new Date().getTime());
    return GM_fetch(url, { method: 'GET' })
        .then((response) => response.text())
        .then((text) => {
            // Initialize the DOM parser
            const parser = new DOMParser();

            // Parse the text
            return parser.parseFromString(text, 'text/html');
        });
}

/**
 * Checks whether a URL is valid and accessible.
 *
 * @param {string} address
 * @returns {Promise<string>}
 */
function isValidURL(address) {
    if (address) {
        try {
            const url = new URL(address);
            return GM_fetch(url, { method: 'HEAD' }).then((response) => {
                if (response.ok) {
                    return address;
                }
                return Promise.reject(address);
            });
        } catch {
            return Promise.reject(address);
        }
    }
    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 | undefined}
 */
function getTheYNCVideoURL(element) {
    const thumbnailURL = element.querySelector('.image > img')?.src;
    if (!thumbnailURL) return;
    for (const [, group_url] of thumbnailURL.matchAll(
        /^https?:\/\/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`;
        }
    }
}

/**
 * Retrieves the video URL from a theYNC video page
 *
 * @param {Element} element
 * @returns {string | undefined}
 */
function retrieveVideoURL(element = document) {
    const archivedScript = element.querySelector(
        '[id=thisPlayer] + script'
    )?.textContent;
    if (archivedScript) {
        for (const [, videoURL] of archivedScript.matchAll(
            /(?<=thisPlayer\.setup\(\{).*?file:\ *"(https?\:\/\/.+?.\.(?:flv|mpg|wmv|avi|3gp|qt|mp4|mov|m4v|f4v))"/gms
        )) {
            if (videoURL) {
                return videoURL;
            }
        }
    }
}

/**
 * Retrieves the video URL from an archived YNC URL
 *
 * @param {string} archiveURL
 * @returns {Promise<string>}
 */
function getVideoURLFromArchive(archiveURL) {
    return GM_fetch(archiveURL, {
        method: 'GET',
    })
        .then((response) => {
            if (!response.ok) {
                console.error(response);
                return Promise.reject(response);
            }
            // When the page is loaded convert it to text
            return response;
        })
        .then((response) => response.text())
        .then((html) => {
            // Initialize the DOM parser
            const parser = new DOMParser();

            // Parse the text
            const doc = parser.parseFromString(html, '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();
        });
}

(() => {
    'use strict';

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

    GM_addStyle(`
    .loader {
        border: 0.25em solid #f3f3f3;
        border-top-width: 0.25em;
        border-top-style: solid;
        border-top-color: hsl(0, 0%, 95.3%);
        border-top: 0.25em solid rgb(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;
    } 
    `);

    waitForElement('[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 (const element of contentBlock.querySelectorAll(
                '.inner-block > a'
            )) {
                const undergroundLogo = element.querySelector(
                    '.item-info > .border-gold'
                );
                if (!undergroundLogo) {
                    continue;
                }
                const loadingElement = document.createElement('div');
                loadingElement.classList.add('loader');
                undergroundLogo.appendChild(loadingElement);
                isValidURL(getTheYNCVideoURL(element))
                    .then(
                        (url) => {
                            undergroundLogo.textContent = 'BYPASSED';
                            undergroundLogo.style.backgroundColor = 'green';
                            element.href = url;
                        },
                        () =>
                            ['com', 'org', 'net']
                                .reduce(
                                    (accumulator, currentTLD) =>
                                        accumulator.catch(() =>
                                            queryArchive(
                                                element.href.replace(
                                                    /(^https?:\/\/theync\.)(com|org|net)(\/.*$)/gim,
                                                    `$1${currentTLD}$3`
                                                )
                                            )
                                        ),
                                    Promise.reject()
                                )
                                .then(
                                    (url) =>
                                        getVideoURLFromArchive(url).then(
                                            (videoURL) => {
                                                undergroundLogo.textContent =
                                                    'ARCHIVED';
                                                undergroundLogo.style.backgroundColor =
                                                    'blue';
                                                element.href = videoURL;
                                            },
                                            () => {
                                                undergroundLogo.textContent =
                                                    'MAYBE ARCHIVED';
                                                undergroundLogo.style.backgroundColor =
                                                    'aqua';
                                                element.href = url;
                                            }
                                        ),
                                    () =>
                                        GM_log(
                                            `No bypass or archive found for ${element.href}`
                                        )
                                )
                    )

                    .finally(() => loadingElement.remove());
            }
        }
    );
    waitForElement('.jw-controlbar-right-group').then((element) => {
        const downloadButton = document.createElement('a');
        downloadButton.classList.add('fa', 'fa-arrow-down');
        downloadButton.addEventListener('click', (event) => {
            const videoURL = retrieveVideoURL();
            if (videoURL) {
                location.href = videoURL;
            }
        });
        element.appendChild(downloadButton);
    });
})();