Thotflix Native Cast Player

Enables native casting, blocks ads, and prevents outdated cache files via automatic cache-buster parameters.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Thotflix Native Cast Player
// @name:de      Thotflix Native Cast Player
// @name:fr      Thotflix Native Cast Player
// @name:es      Thotflix Native Cast Player
// @name:it      Thotflix Native Cast Player
// @name:pt      Thotflix Native Cast Player
// @name:ru      Thotflix Native Cast Player
// @name:zh      Thotflix Native Cast Player
// @name:ja      Thotflix Native Cast Player
// @description      Enables native casting, blocks ads, and prevents outdated cache files via automatic cache-buster parameters.
// @description:de   Ermöglicht natives Casting, blockiert Werbung und verhindert veraltete Cache-Dateien durch automatische Cache-Buster-Parameter.
// @description:fr   Active la diffusion native, bloque les publicités et évite les fichiers de cache obsolètes grâce à des paramètres de suppression du cache automatiques.
// @description:es   Habilita la transmisión nativa, bloquea anuncios y evita archivos de caché obsoletos mediante parámetros automáticos de eliminación de caché.
// @description:it   Abilita il casting nativo, blocca gli annunci e impedisce file di cache obsoleti tramite parametri automatici di pulizia della cache.
// @description:pt   Ativa a transmissão nativa, bloqueia anúncios e evita arquivos de cache desatualizados por meio de parâmetros automáticos de limpeza de cache.
// @description:ru   Включает встроенную трансляцию, блокирует рекламу и предотвращает использование устаревших файлов кэша с помощью автоматических параметров очистки.
// @description:zh   启用原生投屏、拦截广告,并通过自动VLC/Cast缓存清除参数防止缓存过期的视频。
// @description:ja   ネイティブキャストを有効にし、広告をブロックし、自動キャッシュバスターパラメータによって古いキャッシュファイルを防ぎます。
// @version      0.0.6-stable
// @author       Wack.3gp (https://sleazyfork.org/users/4792)
// @copyright    2026+, Wack.3gp
// @namespace    https://sleazyfork.org/users/4792/
// @license      CC BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/
// @icon         https://thotflix.com/wp-content/uploads/2023/03/cropped-Copy-of-Copy-of-vpnanon-1-32x32.png
//
// @match        https://thotflix.com/*
//
// @run-at       document-end
// @grant        GM_info
// @grant        GM_notification
//
// @supportURL   https://sleazyfork.org/scripts/589475/feedback
// @compatible   Chrome tested with Tampermonkey
// @contributionURL https://www.paypal.com/donate/?hosted_button_id=BYW9D395KJWZ2
// @contributionAmount €1.00
// ==/UserScript==

/* jshint esversion: 11 */

(function() {
    'use strict';

    window.open = function() { return null; };

    document.addEventListener('click', function(e) {
        const isVideo = e.target.closest('video');
        const isButton = e.target.closest('button');
        const isLink = e.target.closest('a');
        const isControl = e.target.closest('.fluid_controls');

        if (!isVideo && !isButton && !isLink && !isControl) {
            e.stopPropagation();
            e.preventDefault();
        }
    }, true);

    document.addEventListener('auxclick', function(e) {
        e.stopPropagation();
        e.preventDefault();
    }, true);

    const removeAdContainer = () => {
        const adContainer = document.querySelector('.ts-im-container');
        if (adContainer) {
            adContainer.remove();
        }
    };

    function applyFix() {
        if (document.getElementById('native-cast-player') || document.getElementById('native-error-box')) return;

        const originalVideo = document.querySelector('video.js-fluid-player, video[id^="cfp_"], video');
        if (!originalVideo) return;

        const fluidWrapper = originalVideo.closest('.fluid_video_wrapper') || originalVideo.parentElement;

        const sourceElement = originalVideo.querySelector('source');
        let videoSrc = originalVideo.currentSrc || originalVideo.src || originalVideo.getAttribute('data-cfp-src') || (sourceElement ? sourceElement.src : null);
        
        if (!videoSrc || videoSrc.includes('fig.to')) {
            const allSources = document.querySelectorAll('source, video');
            for (let s of allSources) {
                let src = s.src || s.getAttribute('data-cfp-src') || (s.querySelector && s.querySelector('source') ? s.querySelector('source').src : null);
                if (src && src.includes('.mp4') && !src.includes('fig.to')) {
                    videoSrc = src;
                    break;
                }
            }
        }

        if (!videoSrc || videoSrc.includes('fig.to')) return;

        if ('caches' in window) {
            caches.keys().then(names => {
                names.forEach(name => { caches.delete(name); });
            });
        }

        if (!videoSrc.includes('nocache=')) {
            const separator = videoSrc.includes('?') ? '&' : '?';
            videoSrc = videoSrc + separator + 'nocache=' + Date.now();
        }

        const poster = originalVideo.getAttribute('poster') || originalVideo.getAttribute('data-cfp-poster');

        const newVideo = document.createElement('video');
        newVideo.id = 'native-cast-player';
        newVideo.src = videoSrc;
        if (poster) newVideo.poster = poster;
        newVideo.controls = true;
        
        newVideo.style.cssText = "width:100% !important; height:auto !important; max-height:80vh !important; display:block !important; background:black !important; border:3px solid #4C8BF5 !important; position:relative !important; z-index:999999 !important;";

        newVideo.setAttribute('x-webkit-airplay', 'allow');
        newVideo.setAttribute('webkit-playsinline', 'true');
        newVideo.setAttribute('playsinline', 'true');
        newVideo.disableRemotePlayback = false;

        newVideo.addEventListener('loadedmetadata', function() {
            if (newVideo.duration > 0 && newVideo.duration < 15) {
                const titleElement = document.querySelector('h1.g1-mega.g1-mega-1st.entry-title[itemprop="headline"]');
                let searchTitle = titleElement ? titleElement.innerText.trim() : document.title;
                
                const query = encodeURIComponent(searchTitle + " -site:thotflix.com");
                const googleUrl = `https://www.google.com/search?q=${query}&tbm=vid&safe=off`;

                const errorBox = document.createElement('div');
                errorBox.id = 'native-error-box';
                errorBox.style.cssText = "width:100% !important; height:300px !important; display:flex !important; flex-direction:column !important; align-items:center !important; justify-content:center !important; background:#111 !important; color:#fff !important; border:3px solid #ff4c4c !important; font-family:sans-serif !important; text-align:center !important; position:relative !important; z-index:999999 !important; padding: 20px !important;";
                errorBox.innerHTML = `
                    <div style="font-size:36px; margin-bottom:10px;">⚠️</div>
                    <div style="font-size:18px; font-weight:bold; margin-bottom:5px;">No Full Video Found, Only Ad</div>
                    <div style="font-size:13px; color:#aaa; margin-bottom:15px;">No playable full-length video stream could be detected; the source is shorter than 15 seconds.</div>
                    <a href="${googleUrl}" target="_blank" style="background:#4C8BF5; color:#fff; padding:10px 20px; border-radius:5px; text-decoration:none; font-weight:bold; font-size:14px;">🔍 Search Video on Google</a>
                `;
                if (newVideo.parentNode) {
                    newVideo.parentNode.replaceChild(errorBox, newVideo);
                }
            }
        });

        if (fluidWrapper && fluidWrapper.parentNode) {
            fluidWrapper.parentNode.replaceChild(newVideo, fluidWrapper);
        } else if (originalVideo.parentNode) {
            originalVideo.parentNode.replaceChild(newVideo, originalVideo);
        }

        try {
            newVideo.load();
        } catch (err) {}
    }

    const checkUpdate = () => {
        const currentVersion = GM_info.script.version;
        const lastSeenVersion = localStorage.getItem('thotflix_last_version');
        const donationUrl = GM_info.script.header.match(/@contributionURL\s+(.+)/)?.[1] || "https://www.paypal.com/donate/?hosted_button_id=BYW9D395KJWZ2";

        if (lastSeenVersion !== currentVersion) {
            GM_notification({
                title: `🚀 ${GM_info.script.name} v${currentVersion}`,
                text: `Version ${currentVersion} is live.`,
                timeout: 10000,
                onclick: () => {
                    window.open(donationUrl, "_blank");
                }
            });
            localStorage.setItem('thotflix_last_version', currentVersion);
        }
    };

    const observer = new MutationObserver(() => {
        removeAdContainer();
        const originalVideo = document.querySelector('video.js-fluid-player, video[id^="cfp_"], video');
        if (originalVideo && !document.getElementById('native-cast-player') && !document.getElementById('native-error-box')) {
            applyFix();
        }
    });

    observer.observe(document.documentElement, {
        childList: true,
        subtree: true
    });

    removeAdContainer();
    setTimeout(applyFix, 500);
    setTimeout(checkUpdate, 2000);

    console.log(`${GM_info.script.name}: Fully Loaded`);
})();