Youtube video blocker

Allows you to block videos from youtube

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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();
});