Youtube video blocker

Allows you to block videos from youtube

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Youtube video blocker
// @namespace    Danielv123
// @version      1.4
// @description  Allows you to block videos from youtube
// @author       Danielv123
// @match        https://www.youtube.com/*
// @grant        none
// @require      https://code.jquery.com/jquery.js
// ==/UserScript==

//'use strict';
/*
Videos in sidebar

Get video ID
document.querySelectorAll(".video-list-item .yt-uix-simple-thumb-wrap")[0].dataset.vid
Get video title
document.querySelectorAll(".video-list-item .content-wrapper a:nth-child(1)")[2].title
Get username
document.querySelectorAll(".video-list-item .content-wrapper .stat.attribution span")[2].innerHTML
Hide
document.querySelectorAll(".video-list-item")[2].style.display = "none";
*/
var sidebar = {
    ID: function(x) {
        return document.querySelectorAll(".video-list-item.related-list-item-compact-video > .related-item-dismissable .yt-uix-simple-thumb-wrap")[x].dataset.vid;
    },
    title: function(x) {
        if(document.querySelectorAll(".video-list-item.related-list-item-compact-video > .related-item-dismissable > .content-wrapper a:nth-child(1)")[x]){
            return document.querySelectorAll(".video-list-item.related-list-item-compact-video > .related-item-dismissable > .content-wrapper a:nth-child(1)")[x].title;
        } else {
            return "";
        }
    },
    username: function(x) {
        let y=document.querySelectorAll(".video-list-item.related-list-item-compact-video > .related-item-dismissable > .content-wrapper .stat.attribution span")[x];
        if(y) return y.innerHTML;
        return "";
    },
    hide: function(x) {
        document.querySelectorAll(".video-list-item.related-list-item-compact-video")[x].style.display = "none";
    }
};
/*
Videos on subscriptions page

Get video ID
document.querySelectorAll(".yt-shelf-grid-item > div")[0].dataset.contextItemId
Get video title
document.querySelectorAll(".yt-shelf-grid-item .yt-lockup-content .yt-lockup-title a")[0].title
Get username
document.querySelectorAll(".yt-shelf-grid-item .yt-lockup-content .yt-lockup-byline a")[0].innerHTML
Hide
document.querySelectorAll(".yt-shelf-grid-item")[0].style.display = "none"
*/
var shelfvideos = {
    ID: function(x) {
        return document.querySelectorAll(".yt-shelf-grid-item > div")[x].dataset.contextItemId;
    },
    title: function(x) {
        if(document.querySelectorAll(".yt-shelf-grid-item .yt-lockup-content .yt-lockup-title a")[x] && document.querySelectorAll(".yt-shelf-grid-item .yt-lockup-content .yt-lockup-title a")[x].title){
            return document.querySelectorAll(".yt-shelf-grid-item .yt-lockup-content .yt-lockup-title a")[x].title;
        }
    },
    username: function(x) {
        return document.querySelectorAll(".yt-shelf-grid-item .yt-lockup-content .yt-lockup-byline a")[x].innerHTML;
    },
    hide: function(x) {
        document.querySelectorAll(".yt-shelf-grid-item")[x].style.display = "none";
    }
};
/*
Videos on autolplay list

Title
document.querySelector("#watch-related > li:nth-child(1) > div.related-item-dismissable > div.content-wrapper > a > span.title")
Username
document.querySelector("#watch7-sidebar-modules > div:nth-child(1) > div > div.watch-sidebar-body > ul > li > div.content-wrapper > a > span.stat.attribution > span")

*/
// Code goes here and onwards

function main() {
    if(!localStorage.blockedUsers){
        localStorage.blockedUsers = JSON.stringify([]);
    }
    if(!localStorage.blockedTitles){
        localStorage.blockedTitles = JSON.stringify([]);
    }
    if(!localStorage.blockedRegex){
        localStorage.blockedRegex = JSON.stringify([]);
    }
    //hideSidebarVideos("","PewDiePie");
    //hideShelfVideos("","nigahiga");
    //hideShelfVideos("","","s");

    // Hide videos
    let userFilters = JSON.parse(localStorage.blockedUsers);
    let titleFilters = JSON.parse(localStorage.blockedTitles);
    let regexFilters = JSON.parse(localStorage.blockedRegex);
    // Loop until we are through the largest of our filter sets
    for(let i = 0; i < Math.max(regexFilters.length, Math.max(userFilters.length, titleFilters.length));i++){
        // hideSidebarVideos(id, username, title, regex)
        let id = "";
        let user = userFilters[i] || "";
        let title = titleFilters[i] || "";
        let regex = regexFilters[i] || "";
        hideSidebarVideos(id, user, title);
        hideShelfVideos(id, user, title);
        // only responds to title, but whatever.
        hideEndscreenVideos(undefined, undefined, title);
    }
}
setTimeout(main, 500);
injectScripts();
makeFilterGui();

function makeFilterGui() {
    let HTML =  '<div id="youtubeBlockerGui" style="">';
    // Close button
    HTML += '<div id="youtubeBlockerGuiCloseButton"><img src="http://www.drodd.com/images14/x23.png"></img></div>';
    HTML += '<h1>Youtube Blocker</h1><p>Below are a few textfields. Enter , (comma) seperated lists of titles and/or channels to exclude from your youtube feed.</p><p>Video titles don\'t have to match completely, just having part of the title is enough. Ex "porn,1000 degree,prank" will match "1000 degree knife vs water baloon EPIC"<br>';
    HTML += '<h2>Blocked channels</h2><p><i>All channels with this excact name will be hidden</i></p><textarea id="blockedChannels"></textarea>';
    HTML += '<h2>Blocked videos by name</h2><p><i>All videos with this in their names will be hidden</i></p><textarea id="blockedVideos"></textarea>';
    HTML += '<h2>Regex blocks</h2><p><i>All video titles matching this regex will be hidden</i></p><textarea id="blockedRegex"></textarea>';
    // Save button
    HTML += '<div id="saveButton"><h2>Save</h2></div>';
    // Close everything
    HTML += '</div>';
    // Open settings button in botttom right corner
    HTML += '<div id="gearwheelButton"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Simpleicons_Interface_gear-wheel-in-black.svg/2000px-Simpleicons_Interface_gear-wheel-in-black.svg.png"></img></div>';
    // inject into body
    let container = document.createElement('div');
    container.innerHTML = HTML;
    (document.body || document.head || document.documentElement).appendChild(container);

    // Populate textareas with old settings
    let x = JSON.parse(localStorage.blockedUsers);
    let y = "";
    for(let i in x) {
        y += x[i]+",\n";
    }
    $("textarea#blockedChannels")[0].value = y;
    x = JSON.parse(localStorage.blockedTitles);
    y = "";
    for(let i in x) {
        y += x[i]+",\n";
    }
    $("textarea#blockedVideos")[0].value = y;
    x = JSON.parse(localStorage.blockedRegex);
    y = "";
    for(let i in x) {
        y += x[i]+",\n";
    }
    $("textarea#blockedRegex")[0].value = y;

    document.querySelector("#youtubeBlockerGui #youtubeBlockerGuiCloseButton").onclick = toggleGuiDisplay;
    document.querySelector("#gearwheelButton").onclick = toggleGuiDisplay;
    document.querySelector("#saveButton").onclick = function(){
        console.log("Saving lists of blocked channels, users and regexes");
        // Regex to replace linebreaks
        localStorage.blockedUsers = JSON.stringify($("textarea#blockedChannels")[0].value.replace(/(\r\n|\n|\r)/gm,"").split(","));
        localStorage.blockedTitles = JSON.stringify($("textarea#blockedVideos")[0].value.replace(/(\r\n|\n|\r)/gm,"").split(","));
        localStorage.blockedRegex = JSON.stringify($("textarea#blockedRegex")[0].value.replace(/(\r\n|\n|\r)/gm,"").split(","));
    };
}
function readBlockSettings(){
    return $("#youtubeBlockerGui textarea")[0].value.replace(/(\r\n|\n|\r)/gm,"").split(",");
}
function toggleGuiDisplay(){
    let gui = $("#youtubeBlockerGui")[0];
    if(gui.style.display == "none"){
        gui.style.display = "block";
        console.log("showing blocker gui");
    } else {
        gui.style.display = "none";
        console.log("hiding blocker gui");
    }
}
function injectScripts(){
    // jQuery
    var s=document.createElement('script');
    s.setAttribute('src','https://code.jquery.com/jquery.js');
    document.getElementsByTagName('head')[0].appendChild(s);
    // CSS
    var d=document.createElement('style');
    d.innerHTML = "#youtubeBlockerGui {"+
        "display:none;"+
        "width:60%;"+
        "height:80%;"+
        "background-color:white;"+
        "position:fixed;"+
        "top:10%;"+
        "left:20%;"+
        "border:1px solid lightgrey;"+
        // damn, the youtube sidebar apparently has a z index of 1999999999...
        "z-index:2000000000;"+
        "}"+
        // red X on gui to close
        "#youtubeBlockerGuiCloseButton {"+
        "float:right;"+
        "height:30px;"+
        "width:30px;"+
        "cursor:pointer;"+
        "}"+
        "#youtubeBlockerGuiCloseButton img {"+
        "height:100%;"+
        "width:100%;"+
        "}"+
        // gearwheel in bottom right corner
        "#gearwheelButton {"+
        "position:fixed;"+
        "height:30px;"+
        "width:30px;"+
        "bottom:0px;"+
        "right:0px;"+
        "cursor:pointer;"+
        "}"+
        "#gearwheelButton img {"+
        "height:100%;"+
        "width:100%;"+
        "}"+
        "#saveButton {"+
        "cursor:pointer;"+
        "}"
        ;
    document.getElementsByTagName('body')[0].appendChild(d);
}
function hideShelfVideos(id, username,title, regex) {
    let videos = document.querySelectorAll(".yt-shelf-grid-item");
    for(let i = 0; i < videos.length; i++){
        let hideVideo = function(number, filter){
            // document.querySelectorAll(".yt-shelf-grid-item")[number].style.display = "none";
            shelfvideos.hide(number);
            console.log("Filter: " + filter + " Hidden: " + document.querySelectorAll(".yt-shelf-grid-item .yt-lockup-content .yt-lockup-title a")[number].title);
        };
        // Check by video ID
        if(id && id == shelfvideos.ID(i)){
            hideVideo(i, id);
        }
        // Check by channel name
        if(username && username.toLowerCase() == shelfvideos.username(i).toLowerCase()){
            hideVideo(i, username);
        }
        // Check if video title contains title
        if(title && title != " " && shelfvideos.title(i).toLowerCase().includes(title.toLowerCase())){
            hideVideo(i, title);
        }
        // Check if title matches regex
        if(regex && shelfvideos.title(i).match(regex)){
            hideVideo(i, regex);
        }
    }
}
function hideSidebarVideos(id, username, title, regex) {
    let videos = document.querySelectorAll(".video-list-item");
    for(let i = 0; i < videos.length; i++){
        let hideVideo = function(number, filter){
            // document.querySelectorAll(".video-list-item")[number].style.display = "none";
            sidebar.hide(number);
            console.log("Filter: " + filter + " Hidden sidebar: " + document.querySelectorAll(".video-list-item .content-wrapper a:nth-child(1)")[number].title);
        };
        // Check by video ID
        if(id && id == sidebar.ID(i)){
            hideVideo(i, id);
        }
        // Check by channel name
        if(username && username.toLowerCase() == sidebar.username(i).toLowerCase()){
            hideVideo(i, username);
        }
        // Check if video title contains title
        if(title && title != " " && sidebar.title(i).toLowerCase().includes(title.toLowerCase())){
            hideVideo(i, title);
        }
        // Check if title matches regex
        if(regex && sidebar.title(i).match(regex)){
            hideVideo(i, regex);
        }
    }
}
// hide endscreen videos
function hideEndscreenVideos(id, username, title, regex){
    let x = document.querySelectorAll('[aria-label].ytp-videowall-still.ytp-suggestion-set');
    for(let i = 0;i<x.length;i++){
        // if elemnt has aria-label (lots of those that are unrelated) and includes title and title is not falsey (ex "") beause that would trigger on lots of non video things
        if(x[i].getAttribute("aria-label") && x[i].getAttribute("aria-label").toLowerCase().includes(title) && title){
            x[i].style.display = "none";
            console.log("Filter: "+title+" Hidden: " + x[i].getAttribute("aria-label"));
        }
    }
}


// https://stackoverflow.com/questions/3219758/detect-changes-in-the-dom
var observeDOM = (function(){
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver,
        eventListenerSupported = window.addEventListener;

    return function(obj, callback){
        if( MutationObserver ){
            // define a new observer
            var obs = new MutationObserver(function(mutations, observer){
                if( mutations[0].addedNodes.length || mutations[0].removedNodes.length )
                    callback();
            });
            // have the observer observe foo for changes in children
            obs.observe( obj, { childList:true, subtree:true });
        }
        else if( eventListenerSupported ){
            obj.addEventListener('DOMNodeInserted', callback, false);
            obj.addEventListener('DOMNodeRemoved', callback, false);
        }
    };
})();
observeDOM( document.querySelector("#content") ,function(){
    console.log('dom changed, hiding videos...');
    main();
});