theYNC.com Underground bypass

Watch theYNC Underground videos without needing an account

目前為 2025-01-07 提交的版本,檢視 最新版本

// ==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/*
// @match       *://archive.ph/*
// @match       *://archive.today/*
// @include     /https?:\/\/web\.archive\.org\/web\/\d+?\/https?:\/\/theync\.(?:com|org|net)/
// @grant       GM.xmlHttpRequest
// @connect     media.theync.com
// @connect     archive.org
// @grant       GM_addStyle
// @grant       GM_log
// @grant       GM_addElement
// @version     7.1
// @supportURL  https://gf.qytechs.cn/en/scripts/520352-theync-com-underground-bypass/feedback
// @license     MIT
// @author      https://gf.qytechs.cn/en/users/1409235-paywalldespiser
// ==/UserScript==

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

        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (!(node instanceof HTMLElement)) continue;

                    if (node.matches(selector)) {
                        observer.disconnect();
                        resolve(node);
                        return;
                    }
                    const childElement = node.querySelector(selector);
                    if (childElement) {
                        observer.disconnect();
                        resolve(childElement);
                        return;
                    }
                }
            }
        });

        observer.observe(target, {
            childList: true,
            subtree: true,
            attributes: false,
            characterData: false,
        });
    });
}

/**
 * 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) {
    if (location.host === 'archive.ph' || location.host === 'archive.today') {
        const videoElement = element.querySelector(
            '[id="thisPlayer"] video[old-src]'
        );
        if (videoElement) {
            return videoElement.getAttribute('old-src');
        }
    }
    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(
            /(?<=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();
        });
}

/**
 * set Link of element
 *
 * @param {HTMLLinkElement} element
 * @param {string} url
 */
function setElementLink(element, url) {
    element.href = url;
    element.target = '_blank';
    element.rel = 'noopener noreferrer';
}

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

    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 (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';
                        setElementLink(element, 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';
                                            setElementLink(element, videoURL);
                                        },
                                        () => {
                                            undergroundLogo.textContent =
                                                'MAYBE ARCHIVED';
                                            undergroundLogo.style.backgroundColor =
                                                'aqua';
                                            setElementLink(element, url);
                                        }
                                    ),
                                () =>
                                    GM_log(
                                        `No bypass or archive found for ${element.href}`
                                    )
                            )
                )

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址