Greasy Fork

Greasy Fork is available in English.

MafiaBlackboxes

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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