Thotflix Native Cast Player

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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`);
})();