MafiaBlackboxes

Hides the stupid FIIM spoilers by mercilessly covering them with black boxes. Tested in Chrome and Firefox on YouTube and Twitch.

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.

Necesitarás 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.

Necesitará instalar una extensión como Tampermonkey para 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)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

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

// ==UserScript==
// @name         MafiaBlackboxes
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  Hides the stupid FIIM spoilers by mercilessly covering them with black boxes. Tested in Chrome and Firefox on YouTube and Twitch.
// @author       Kirill Khazan <[email protected]>
// @match        https://*.youtube.com/*
// @match        https://youtube.com/*
// @match        https://*.youtube.com/*
// @match        https://*.youtu.be/*
// @match        https://youtu.be/*
// @match        https://*.twitch.tv/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    //    'use strict';
    var debounceData = {};
    var bbUninstallers = [];
    var tbUninstallers = [];
    var nAttempts = 0;

    function updateOnBoxEvents(uninstallers, fupdate) {
        // Function to update the black box when the video box is resized
        const video = document.querySelector('video');
        if(video == null) {
            return;
        }

        // Monitor changes in the video box size using ResizeObserver
        const resizeObserver = new ResizeObserver(() => {
            fupdate();
        });
        resizeObserver.observe(video);
        resizeObserver.observe(video.parentElement);
        uninstallers.push(() => resizeObserver.disconnect());

        const scrollHandler = () => {
            //console.log("win scrolled");
            fupdate();
        };
        window.addEventListener('scroll', scrollHandler);
        uninstallers.push(() => window.removeEventListener('scroll', scrollHandler));


        const intersectionObserver = new IntersectionObserver(fupdate, {
            root: null,
            threshold: buildThresholdList(),
        });
        intersectionObserver.observe(video);
        uninstallers.push(() => intersectionObserver.disconnect());
    }


    function videoDimensions(video) {
        // Ratio of the video's intrisic dimensions
        var videoRatio = video.videoWidth / video.videoHeight;
        // The width and height of the video element
        var width = video.offsetWidth, height = video.offsetHeight;
        // The ratio of the element's width to its height
        var elementRatio = width/height;
        // If the video element is short and wide
        if(elementRatio > videoRatio) width = height * videoRatio;
        // It must be tall and thin, or exactly equal to the original ratio
        else height = width / videoRatio;
        return {
            width: width,
            height: height
        };
    }

    function removeBlackBox(boxId) {
        const existingBlackBox = document.getElementById(boxId);
        if (existingBlackBox) {
            existingBlackBox.remove();
        }
    }

    function buildThresholdList() {
        let thresholds = [];
        let numSteps = 2000;

        for (let i = 1.0; i <= numSteps; i++) {
            let ratio = i / numSteps;
            thresholds.push(ratio);
        }

        thresholds.push(0);
        return thresholds;
    }

    function enableBlackBoxes() {
        console.log('enableBlackBoxes');
        bbEnabled = 1;

        function _updateBoxPosition(boxId, topRatio, leftRatio, widthRatio, heightRatio) {
            const blackBox = document.getElementById(boxId);
            if (!blackBox) {
                return;
            }

            console.log('updateBoxPosition');

            const video = document.querySelector('video'); // Selecting the video element on the page
            const videoBox = video.getBoundingClientRect();

            const actual = videoDimensions(video);
            const innerOffsetLeft = video.offsetLeft + (videoBox.width - actual.width) / 2;
            const innerOffsetTop = video.offsetTop + (videoBox.height - actual.height) / 2;

            //console.log(`VVV ${videoBox.top}, ${videoBox.left}, ${videoBox.height}, ${videoBox.width}, `);
            blackBox.style.position = 'absolute';
            blackBox.style.backgroundColor = 'black';
            blackBox.style.opacity = '1';

            blackBox.style.top = `${innerOffsetTop + actual.height * topRatio}px`;
            blackBox.style.left = `${innerOffsetLeft + actual.width * leftRatio}px`;
            blackBox.style.width = `${actual.width * widthRatio}px`;
            blackBox.style.height = `${actual.height * heightRatio}px`;

            blackBox.style.pointerEvents = 'none'; // To allow clicks to pass through
            blackBox.style.zIndex = '2'; // Ensure it's above other elements
        }

        var debounceData = {};

        function debounce(fn, boxId, ms = 0) {
            return function(...args) {
                const exec = function() {
                    delete debounceData[boxId];
                    fn.apply(this, args);
                };

                clearTimeout(debounceData[boxId]);
                debounceData[boxId] = setTimeout(() => exec(), ms);
            };
        }

        function updateBoxPosition(boxId, topRatio, leftRatio, widthRatio, heightRatio) {
            console.log(`debounce updateBoxPosition ${boxId}`);
            debounce(_updateBoxPosition, boxId, 30)(boxId, topRatio, leftRatio, widthRatio, heightRatio);
        }


        function createBlackBox(boxId, topRatio, leftRatio, widthRatio, heightRatio) {
            const video = document.querySelector('video'); // Selecting the video element on the page

            // Remove any existing black box
            removeBlackBox(boxId);

            var playerControls = document.querySelector('.ytp-chrome-bottom') || document.querySelector('.player-controls');
            playerControls.style.zIndex = '99'

            const blackBox = document.createElement('div');
            blackBox.id = boxId;

            updateBoxPosition(boxId, topRatio, leftRatio, widthRatio, heightRatio);
            //document.getElementsByClassName('video-stream html5-main-video')[0].parentElement.appendChild(blackBox);

            video.parentElement.appendChild(blackBox);
            //document.body.appendChild(blackBox);
        }

        function manageBlackBox(boxId, topRatio, leftRatio, widthRatio, heightRatio) {
            // Call the function to initially create the black box
            createBlackBox(boxId, topRatio, leftRatio, widthRatio, heightRatio);
            // Call the function to update the black box on video box resize
            // updateBlackBoxOnEvents();
            updateOnBoxEvents(bbUninstallers, () => updateBoxPosition(boxId, topRatio, leftRatio, widthRatio, heightRatio));
        }

        manageBlackBox('bottomBlackBox', 0.75, 0, 1, 0.25);
        manageBlackBox('topLeftBlackBox', 0, 0, 0.2, 0.16);
        manageBlackBox('topTopBlackBox', 0, 0, 0.65, 0.08);
        manageBlackBox('rightBlackBox', 0, 0.85, 0.15, 0.25);
    }



    function disableBlackBoxes() {
        bbEnabled = false;
        console.log('disableBlackBoxes');
        removeBlackBox('bottomBlackBox');
        removeBlackBox('topLeftBlackBox');
        removeBlackBox('topTopBlackBox');
        removeBlackBox('rightBlackBox');

        for (var boxId in Object.keys(debounceData)) {
            clearTimeout(debounceData[boxId]);
            delete debounceData[boxId];
        }

        bbUninstallers.forEach((u) => u());
        bbUninstallers = [];
    }


    var bbEnabled = false;
    function toggleBlackBoxes() {
        const button = document.getElementById('bbToggleButton');
        if(bbEnabled){
            disableBlackBoxes();
            //button.style.opacity = 0;
            button.textContent = 'Hide roles';
        } else {
            enableBlackBoxes();
            //button.style.opacity = 1;
            button.textContent = 'Show roles';
        }
    }

    function updateToggleButton() {
        console.log("updToggleButton");
        const video = document.querySelector('video');
        const button = document.getElementById('bbToggleButton');
        const videoBox = video.getBoundingClientRect();
        const actual = videoDimensions(video);
        const innerOffsetLeft = video.offsetLeft + (videoBox.width - actual.width) / 2;
        const innerOffsetTop = video.offsetTop + (videoBox.height - actual.height) / 2;

        //console.log(`VVV ${videoBox.top}, ${videoBox.left}, ${videoBox.height}, ${videoBox.width}, `);
        //console.log(`ZZZ ${innerOffsetTop}, ${actual.height}`);
        //console.log(`ZZZ ${video.offsetTop}, ${videoBox.height}, ${actual.height}`);
        //button.style.top = `${innerOffsetTop + 5}px`;
        if(video.offsetTop < 0) {
            button.style.top = `${videoBox.height * 0.05}px`;
            button.style.left = `px`;
        } else {
            button.style.top = `${innerOffsetTop + actual.height * 0.05}px`;
            button.style.left = `${innerOffsetLeft + 5}px`;
        }
    }

    function createToggleButton() {
        const video = document.querySelector('video');
        console.log(`YYY video is OK`);

        const button = document.createElement('button');
        button.id = 'bbToggleButton';
        button.textContent = 'Hide roles';
        button.style.cssText = `
            position: absolute;
            background-color: green;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            transition: opacity 0.2s ease-in-out;
            z-index: 200;
            opacity: 0;
            top: 5;
            left: 5;
            width: 200px;
            height: 80px;
            text-align: center;
        `;

        video.parentElement.appendChild(button);

        button.addEventListener('mouseover', () => {
            button.style.opacity = 1;
        });

        button.addEventListener('mouseout', () => {
            //button.style.opacity = bbEnabled ? 1 : 0;
            button.style.opacity = 0;
        });

        button.addEventListener('click', (event) => {
            event.stopPropagation();
            event.stopImmediatePropagation();
            event.preventDefault();
            toggleBlackBoxes();
            return false;
        });

        updateToggleButton();
    }

    function manageToggleButton() {
        const video = document.querySelector('video');
        if(video == null) {
            //console.log(`XXX video == null; ${document.body.childElementCount}`);
            if(nAttempts++ < 1000) {
                setTimeout(() => manageToggleButton(), 50);
            }
            return;
        }

        console.log(`YYY video is OK`);

        createToggleButton();
        updateOnBoxEvents(tbUninstallers, updateToggleButton);
    }

    function removeToggleButton() {
        const existingButton = document.getElementById('bbToggleButton');
        if (existingButton) {
            existingButton.remove();
        }
        tbUninstallers.forEach((u) => u());
        tbUninstallers = [];
    }

    const observeUrlChange = () => {
        let oldHref = document.location.href;
        const body = document.querySelector("body");
        const observer = new MutationObserver(mutations => {
            if (oldHref !== document.location.href) {
                oldHref = document.location.href;
                console.log('LOADDDDD');
                nAttempts = 0;
                disableBlackBoxes();
                removeToggleButton();
                manageToggleButton();
            }
        });
        observer.observe(body, { childList: true, subtree: true });
    };

    //window.onload = observeUrlChange;
    observeUrlChange();

    manageToggleButton();
})();