屏蔽所有Youtube广告 Youtube AD blocker (Block all ad). Including blocking Youtube Music.

Block all video ads, insert ads, page ads. Including Youtube Music. 屏蔽所有视频广告、插入广告、页面广告。包括Youtube Music

Verzia zo dňa 11.04.2022. Pozri najnovšiu verziu.

// ==UserScript==
// @name         屏蔽所有Youtube广告 Youtube AD blocker (Block all ad). Including blocking Youtube Music.   
// @name:zh-CN   屏蔽所有Youtube广告 包括Youtube Music
// @namespace    https://github.com/ChenZihan-sudo/Youtube-AD-blocker/
// @version      0.5 [Beta]
// @description  Block all video ads, insert ads, page ads. Including Youtube Music. 屏蔽所有视频广告、插入广告、页面广告。包括Youtube Music
// @description:zh-CN  屏蔽所有视频广告、插入广告、页面广告
// @author       ChenZihan
// @match        *.youtube.com/*
// @icon
// @grant        none
// ==/UserScript==
(function () {
    let rmAry = new Array();
    let { href } = location;

    setInterval(() => {
        // console.log("execute");
        let bufHref = location.href; //detect the change of href and execute 
        let detectAd; //detect the activity of insert ad
        if (document.getElementsByClassName("ytp-ad-preview-text").length) {
            detectAd = (document.getElementsByClassName("ytp-ad-preview-text")[0].innerHTML.constructor == String);
        }

        if (bufHref != href || detectAd) {
            console.log("[YT AD block] Detect the change of href", href);
            href = bufHref;
            executeManager('f');
            // frameworkAlreadyAdded = false; //From close video AD, change the frameworkAlreadyAdded to be false when href changed.
        }
    }, 500);

    if (location.hostname.search("youtube") != -1) {

        // action - companion - ad - info - button
        addBData('id', 'player-ads', 'f'); //add data
        addBData('id', 'masthead-ad', 'f');
        addBData('path', '#YtKevlarVisibilityIdentifier', 'f');
        addBData('class', 'ytp-ad-overlay-container', 'a');
        addBData('csClick', [['ytp-ad-feedback-dialog-reason-input', 'randomPara(0, 2)'], ['ytp-ad-feedback-dialog-confirm-button', '0'], 1], 'f');
        addBData('tag', 'ytd-display-ad-renderer', 'a');
        addBData('csClick', [['ytp-ad-skip-button', '0'], 0], 'a');
        // addBData("exe", "skipFixedAD();frameworkAlreadyAdded = true;", 'f');
        addBData("remTactic1", ["badge-style-type-ad", 7, "ytd-section-list-renderer"], 'f');
        addBData("remTactic1", ["badge-style-type-ad", 5, "ytd-item-section-renderer"], 'f');
        addBData("remTactic1", ["badge-style-type-ad", 5, "ytd-watch-next-secondary-results-renderer"], 'f');


        executeManager(); //execute at first time
    }


    /**
     * @param {String} exePara (undefined) or ('f' => execute the data for 20 times, every time interval 500ms )
     * */
    function executeManager(exePara) {
        let len = rmAry.length;


        for (let i = 0; i < len; i++) {
            if (rmAry[i][2] == 'a' && exePara == undefined) {

                setInterval(() => {
                    console.log("[YT AD block] Execute remove insert AD loop");
                    execute(); //always execute the data
                }, 800);

            } else if (rmAry[i][2] == 'f' || exePara == 'f') { //execute only in first time

                console.log("[YT AD block] Execute remove AD");

                window.onload = function () { intervalExecute(); } //if player already load
                if (exePara == 'f') { intervalExecute(); }
                intervalExecute();
                function intervalExecute() {
                    let times = 0;
                    let timer = setInterval(() => {
                        times++; if (times > 20) { clearInterval(timer); };
                        execute(); //excute 20 times in every 500ms
                    }, 500);
                }
            }

            function execute() {
                let isUniExeTag = rmAry[i][0] == "universal"; //Check is universal tag or not
                if (!isUniExeTag) {
                    let exeTag = rmAry[i][0]; //Execute Type
                    let exeData = rmAry[i][1]; //Data container of execute type
                    executeBase(exeTag, exeData);
                } else {
                    //[Beta] Execute Type: universal
                    // let exeLength = rmAry[i][1].length;
                    // for (let layeri = 0; layeri < exeLength; layeri++) {
                    //     let layerExeData = rmAry[i][1][layeri];
                    //     let exeTag = layerExeData[0];
                    //     let exeData = layerExeData[1];
                    //     executeBase(layerExeData, exeTag, exeData);
                    // }
                };
            }


            function executeBase(exeTag, exeData) {
                let cckClassDataPosi, cckType, cckCs;
                switch (exeTag) {
                    case 'id': idRm(exeData);
                        break;
                    case 'path': pathRm(exeData);
                        break;
                    case 'class': classRm(exeData);
                        break;
                    case 'csClick':
                        {
                            len = rmAry[i][1].length - 1; //find last one num
                            cckClassDataPosi = rmAry[i][1][len];
                            cckType = rmAry[i][1][len].constructor;

                            if (cckType == Number) {
                                cckCs = rmAry[i][1][cckClassDataPosi][0];
                            } else if (cckType == String) {
                                cckCs = rmAry[i][1][2];
                            }

                            csClick(cckCs, rmAry[i][1]);
                            break;
                        }
                    case 'tag': tagRm(exeData);
                        break;
                    case 'exe': { dataExe(exeData); }
                        break;
                    case 'remTactic1': {
                        let className = exeData[0];
                        let parentNum = exeData[1];
                        let expParentClassName = exeData[2];
                        remTactic1(className, parentNum, expParentClassName);
                    }
                        break;
                }
            }
        }
    }

    function addUniData(d2, d3) {
        addBData('universal', d2, d3);
    }

    function addExeData(d2, d3) {
        addBData('exe', d2, d3);
    }

    /**
     * @param d1 - Execute Type: 'id' => Use document.getElementById() to remove
     * @param d1 - Execute Type: 'path' => Use document.querySelector() to remove
     * @param d1 - Execute Type: 'class' => Use document.getElementsByClassName() to remove 
     * @param d1 - Execute Type: 'csClick' => css click event
     * @param d1 - Execute Type: 'tag' => Use document.getElementsByTagName() to remove
     * @param d1 - Execute Type: 'exe' => Use eval to execute the code
     * @param d1 - Execute Type: 'remTactic1' => See remTactic1();
     * @param d2 - Data container of d1 execute type
     * @param d3 - 'a' => Always execute the data, interval 800ms
     * @param d3 - 'f' => First execute the data for 20 times, every time interval 500ms
     */
    function addBData(d1, d2, d3) {
        let len = apdAry(rmAry);
        rmAry[len][0] = d1;
        rmAry[len][1] = d2;
        rmAry[len][2] = d3;
        rmAry[len][3] = rmAry.length - 1;
    }

    /**
     * By finding a className to find higher level of its parent element by class name and remove it
     * @param {String} className The child node class name (signature class name recommend)
     * @param {Number} parentNum How many node layers it parent elements have to get className(child node)
     * @param {String} expParentClassName The parent node class name(signature class name recommend)
     */
    function remTactic1(className, parentNum, expParentClassName) {
        var sl_length = document.getElementsByClassName(className).length;
        if (sl_length != 0) {
            if (parentNum == 0) {
                var len = document.getElementsByClassName(className).length;
                for (let i = 0; i < len; i++) {
                    document.getElementsByClassName(className)[0].remove();
                }
            } else {
                for (var s_i = 0; s_i < sl_length; s_i++) {
                    var parent = ".parentElement";
                    var finalParent = "";
                    for (var p_i = 0; p_i < parentNum; p_i++) {
                        finalParent = finalParent + parent;
                    }

                    //Find the parent name
                    var parentNode = "document.getElementsByClassName('" + className + "')[0]" + finalParent;
                    var parentNodeClassListLength = eval(parentNode + ".classList.length");
                    var isParentClassName = false;
                    for (let ii = 0; ii < parentNodeClassListLength; ii++) {
                        var parentClassName = eval(parentNode + ".classList[" + ii + "]");
                        if (parentClassName == expParentClassName) {
                            isParentClassName = true;
                            break;
                        }
                    }

                    if (isParentClassName) {
                        eval("document.getElementsByClassName('" + className + "')[0]" + finalParent + ".remove();");
                        remTactic1(className, parentNum, expParentClassName);
                        break;
                    }
                }
            }
        }
    }

    function csClick(cckCs, exAry) {
        if (document.getElementsByClassName(cckCs).length != 0) {
            let len = exAry.length;
            for (let i = 0; i < len; i++) {
                if (exAry[i].constructor != Array) { break; } //cck data type
                document.getElementsByClassName(exAry[i][0])[eval(exAry[i][1])].click(); //choose feedback option
            }
        }
    }

    function classRm(_class) {
        let times = 0;
        while (document.getElementsByClassName(_class).length != 0) {
            times++; if (times > 100) { break; }
            document.getElementsByClassName(_class)[0].remove();
        }
    }

    function idRm(id) {
        if (document.getElementById(id) != null) {
            document.getElementById(id).remove();
        }
    }

    function pathRm(path) {
        if (document.querySelector(path) != null) {
            document.querySelector(path).remove();
        }
    }

    function tagRm(tag) {
        let times = 0;
        while (document.getElementsByTagName(tag).length != 0) {
            times++; if (times > 100) { break; }
            document.getElementsByTagName(tag)[0].parentNode.parentNode.remove();
        }
    }

    function dataExe(exeData) {
        if (exeData) {
            let isArray = exeData.constructor == Array;
            if (!isArray) {
                eval(exeData);
            } else {
                //[Beta Test] Execute Type: universal
                if (exeData[0]) {
                    let finalData = exeData[0] + exeData[1];
                    eval(finalData);
                }
            }
        }
    }

    function randomPara(min, max) {
        return Math.floor(Math.random() * (max - min + 1)) + min;
    }

    function apdAry(ary) {
        ary.splice(ary.length, 0, []);
        return ary.length - 1;
    }

    // var frameworkAlreadyAdded = false;
    // function optimizeUserExperience(type) {
    //     if (type == 1) {
    //         //Let user can't see this about ad card
    //         let adCard = document.getElementsByTagName("yt-about-this-ad-renderer")[0];
    //         if (adCard) { adCard.style.display = "none"; };

    //         //Let user can't see this backdrop
    //         let backdrop = document.getElementsByTagName("tp-yt-iron-overlay-backdrop")[0];
    //         if (backdrop) { backdrop.style.display = "none"; }
    //     }

    //     if (type == 2) {
    //         //Check the current path
    //         if (location.pathname.search("watch") != -1) {
    //             //click the play button when it load fininshed, avoid stopping in ad video. 
    //             let playButton = document.getElementsByClassName("ytp-play-button")[0];
    //             let isClick = document.getElementsByClassName("ytp-ad-preview-container").length;
    //             if (playButton && isClick) { playButton.click(); }
    //         }
    //     }

    //     if (type == 3) {
    //         //Set backdrop can be seen when ad is removed
    //         let backdrop = document.getElementsByTagName("tp-yt-iron-overlay-backdrop")[0];
    //         if (!backdrop) {
    //             backdrop.style.display = "";
    //         }
    //     }
    // }


    // /*————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    //  Thanks for the contribution at https://github.com/ChenZihan-sudo/Youtube-AD-blocker/pull/2
    //  Author: Naccl
    //  AuthorLink: https://github.com/Naccl
    //  Function: skipFixedAD()
    // ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————*/
    // function skipFixedAD() {
    //     if (!frameworkAlreadyAdded) {
    //         console.log("triggered2");
    //         let adClickable, times = 0;
    //         let timer = setInterval(() => {
    //             //open the feedback iframe
    //             times++;
    //             adClickable = document.getElementsByClassName('ytp-ad-clickable').length;
    //             if (adClickable || times > 20) {
    //                 clearInterval(timer);
    //             };
    //         }, 500);

    //         if (adClickable) {
    //             optimizeUserExperience(1);
    //             console.log("triggered3");

    //             document.getElementsByClassName('ytp-ad-clickable')[0].click();

    //             const observerInit = { childList: true, subtree: true };

    //             //In the first time, wo should detect the iframe element.
    //             //After the first time, the iframe element will always exist.
    //             const iframeObserver = new MutationObserver(() => {
    //                 console.log("triggered4");
    //                 const iframe = document.getElementById('iframe');
    //                 if (iframe) {
    //                     iframeObserver.disconnect();

    //                     function iframeLoaded() {
    //                         iframe.removeEventListener('load', iframeLoaded, true);

    //                         //get feedback iframe document
    //                         const iframeDocument = iframe.contentWindow.document;
    //                         //click feedback button to open the confirm dialog (the element id or class are random, so we need XPath)
    //                         document.evaluate('/html/body/c-wiz/div/div/div[2]/div[2]/div/div[1]/div[1]/div/div[2]/div[2]/div/button', iframeDocument).iterateNext().click();

    //                         const iframeBody = document.evaluate('/html/body', iframeDocument).iterateNext();
    //                         const okBtnObserver = new MutationObserver(() => {
    //                             //get ok button after dialog created
    //                             const okBtn = document.evaluate('/html/body/div[2]/div/div[2]/span/div/div/div[2]/div[2]/button', iframeDocument).iterateNext();
    //                             if (okBtn) {
    //                                 okBtnObserver.disconnect();
    //                                 okBtn.click();

    //                                 //There may be delay and we need to detect the ok tips.
    //                                 //But ComputedStyle has not change event, MutationObserver can't do it.
    //                                 let okTipsTimer = setInterval(() => {
    //                                     //If the feedback is done, it will change from 'none' to 'flex'.
    //                                     const finishedTipsDisplay = getComputedStyle(document.evaluate('/html/body/c-wiz/div/div/div[2]/div[1]', iframeDocument).iterateNext(), null).display;
    //                                     if (finishedTipsDisplay !== 'none') {
    //                                         clearInterval(okTipsTimer);
    //                                         //Finished, close the iframe, the video will continue automatically.
    //                                         document.evaluate('/html/body/c-wiz/div/div/div[1]/div[2]/div/button', iframeDocument).iterateNext().click();
    //                                         // optimizeUserExperience(2); //click the play button when it load fininshed, avoid stopping in ad video. 
    //                                         // optimizeUserExperience(3); //reset the backdrop element
    //                                     }
    //                                 }, 50);
    //                             }
    //                         });
    //                         okBtnObserver.observe(iframeBody, observerInit);
    //                     }
    //                     //listen for the iframe element load event
    //                     iframe.addEventListener('load', iframeLoaded, true);
    //                 }
    //             });
    //             iframeObserver.observe(document, observerInit);
    //         };
    //     }
    // }
    // //————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
})();