Chaturbate CDN Switcher

锁定 edge23-sea 节点以解决因 CDN 自动分配导致的视频 404 错误或播放卡顿问题,并提供一键切换开关。

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Chaturbate CDN Switcher
// @namespace    mmcdn-edge-switcher
// @version      2.5
// @description  锁定 edge23-sea 节点以解决因 CDN 自动分配导致的视频 404 错误或播放卡顿问题,并提供一键切换开关。
// @author       RYUJO
// @license      MIT
// @match        https://chaturbate.com/*
// @match        https://*.chaturbate.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=chaturbate.com
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const TARGET = 'edge23-sea.live.mmcdn.com';
    const REGEX = /([a-z0-9-]+)\.live\.mmcdn\.com/gi;
    
    // 状态管理:锁定 edge23-sea 节点解决 404 错误
    if (localStorage.getItem('mmcdn_force_mode') === null) localStorage.setItem('mmcdn_force_mode', 'true');
    let isEnabled = localStorage.getItem('mmcdn_force_mode') === 'true';

    // === 1. RYUJO 水印 (仅保留在控制台) ===
    const RYUJO_ART = `
██████╗ ██╗   ██╗██╗   ██╗     ██╗ ██████╗ 
██╔══██╗╚██╗ ██╔╝██║   ██║     ██║██╔═══██╗
██████╔╝ ╚████╔╝ ██║   ██║     ██║██║   ██║
██╔══██╗  ╚██╔╝  ██║   ██║██   ██║██║   ██║
██║  ██║   ██║   ╚██████╔╝╚█████╔╝╚██████╔╝
╚═╝  ╚═╝   ╚═╝    ╚═════╝  ╚════╝  ╚═════╝ `;

    console.log(`%c${RYUJO_ART}`, 'color: #06b6d4; font-weight: bold; font-family: monospace;');
    console.log(
        `%c RYUJO %c CDN Switcher %c ${isEnabled ? 'ACTIVE' : 'DISABLED'} `,
        'background:#0891b2; color:white; padding:3px 5px; border-radius:3px 0 0 3px; font-weight:bold;',
        'background:#334155; color:white; padding:3px 5px;',
        `background:${isEnabled ? '#10b981' : '#ef4444'}; color:white; padding:3px 5px; border-radius:0 3px 3px 0; font-weight:bold;`
    );

    // === 2. 核心拦截逻辑 (解决 404 问题) ===
    function fixUrl(str) {
        if (!isEnabled || typeof str !== 'string') return str;

        // 仅拦截视频流相关请求,不干扰图片加载
        const isStream = /\.m3u8($|\?)/i.test(str) || /\.ts($|\?)/i.test(str);
        
        if (isStream && str.indexOf('.live.mmcdn.com') !== -1 && str.indexOf(TARGET) === -1) {
            const newUrl = str.replace(REGEX, TARGET);
            
            console.groupCollapsed(`%c ⚡ RYUJO HIJACK %c Stream Redirected `, 'background:#06b6d4;color:white;padding:1px 4px;border-radius:3px;', 'color:#06b6d4;font-weight:bold;');
            console.log('%cOriginal:%c ' + str, 'color:#94a3b8;', 'color:#ef4444;text-decoration:line-through;');
            console.log('%cTarget:  %c ' + newUrl, 'color:#94a3b8;', 'color:#10b981;font-weight:bold;');
            console.groupEnd();
            
            return newUrl;
        }
        return str;
    }

    // --- A. Hls.js Hook ---
    Object.defineProperty(window, 'Hls', {
        configurable: true, enumerable: true,
        get: function() { return this._ryujo_hls; },
        set: function(val) {
            this._ryujo_hls = val;
            if (val && val.prototype && !val.prototype._patched) {
                const oldLoad = val.prototype.loadSource;
                val.prototype.loadSource = function(url) { return oldLoad.call(this, fixUrl(url)); };
                val.prototype._patched = true;
            }
        }
    });

    // --- B. 网络层 Hooks ---
    const _fetch = window.fetch;
    window.fetch = function (i, n) {
        if (typeof i === 'string') i = fixUrl(i);
        else if (i instanceof Request) i = new Request(fixUrl(i.url), i);
        return _fetch.call(this, i, n);
    };

    const _open = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function (m, u, ...a) {
        return _open.apply(this, [m, fixUrl(u), ...a]);
    };

    // === 3. 极简指示灯 UI (提供一键切换开关) ===
    function injectUI() {
        if (document.getElementById('ryujo-dot')) return;
        const dot = document.createElement('div');
        dot.id = 'ryujo-dot';
        dot.style.cssText = `
            position: fixed; top: 12px; right: 12px; z-index: 2147483647;
            width: 10px; height: 10px; border-radius: 50%;
            background: ${isEnabled ? '#06b6d4' : '#475569'};
            cursor: pointer; border: 2px solid #fff;
            box-shadow: 0 0 8px ${isEnabled ? '#06b6d4' : 'transparent'};
            transition: 0.3s;
        `;
        dot.title = `Chaturbate CDN Switcher: ${isEnabled ? 'ON' : 'OFF'}`;
        dot.onclick = () => {
            localStorage.setItem('mmcdn_force_mode', !isEnabled);
            location.reload();
        };
        (document.body || document.documentElement).appendChild(dot);
    }

    if (document.readyState === 'loading') window.addEventListener('DOMContentLoaded', injectUI);
    else injectUI();
    setInterval(injectUI, 3000);

})();