MafiaBlackboxes

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

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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