Chaturbate CDN Switcher

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

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

})();