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