theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

Από την 17/01/2025. Δείτε την τελευταία έκδοση.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

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.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

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