Youtube留言黑名單

屏蔽黑名單內頻道在其他影片下的留言,可以查看和移除黑名單內的頻道。

目前为 2021-07-04 提交的版本。查看 最新版本

// ==UserScript==
// @name         Youtube留言黑名單
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  屏蔽黑名單內頻道在其他影片下的留言,可以查看和移除黑名單內的頻道。
// @author       Microdust
// @match        https://*.youtube.com/*
// @icon         https://www.google.com/s2/favicons?domain=youtube.com
// @grant        GM_getResourceText
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/sweetalert2.all.min.js
// ==/UserScript==

(function() {
    'use strict';

    //以下設定將在重載網頁後生效

    //是否刪除在黑名單內的留言true=刪除/false=不刪除留言但用deleteText裡的文字覆蓋
    const deleteComment = true;
    //不刪除留言時用deleteText裡的文字覆蓋
    const deleteText = "留言被屏蔽";
    //#黑名單-導出的檔名(不可為空)
    const exportName = "黑名單";
    //#黑名單-佔整個畫面的寬度比例
    const blacklistWidth = "50%";
    //#黑名單-名字顯示最長字數(超過此限制將會以'...'省略,數字為負將不限制)
    const nameLength = -1;
    //留言最小字數過濾,小於0則不限制(需小於留言最大字數)
    const commentMinLength = 0;
    //留言最大字數過濾,小於0則不限制(需大於留言最小字數)
    const commentMaxLength = 0;
    //關鍵字過濾
    const banWords=[
        //    "將要過濾的關鍵字填入雙引號中","刪除註解以啟用過濾","可自由增減關鍵字"
    ];
    //
    //以下為範例:
    //---------------------------------
    //-   const banWords=[
    //-       "關","鍵字","範例"
    //-   ];
    //---------------------------------
    //
    let btnSetting;
    let prelink;
    let link;
    let thisPath;
    let text;
    let viewURL;
    let workArea=true;
    let list = [];
    //
    let checkedComment;
    let path;
    let comment;
    let commentUserId;
    let commentUserName;
    let commentValue;
    let btnBlackList;
    let precomment=[];
    //
    let threadPath;
    let checkedReply;
    let replyPath;
    let reply;
    let replyUserId;
    let replyUserName;
    let replyValue;
    let replyBtn;
    let replyCount=[];
    let replyBan=[];
    let noReply=[];

    if(GM_getValue("list")) list=blacklist("init");
    //list.length=0;
    //blacklist("save");
    "/ytd-comment-thread-renderer[15]/div/ytd-comment-replies-renderer/div[1]/ytd-button-renderer[1]/a/tp-yt-paper-button/yt-formatted-string"
    setInterval(function(){
        link = window.location.href;
        if(prelink!=link) {
            prelink = window.location.href;
            workArea = true;
            checkedComment = 0;
            viewURL=false;
            noReply.length=0;
        }
        if(workArea) workArea=fnNameListCheck();
    }, 1000);

    function fnNameListCheck(){
        let pathName=window.location.pathname.split('/');
        if(!viewTypeFn(pathName,window.location.search.toString())) return false;
        if(checkedComment) btnSettingFn();
        while(getComment()){
            defData();
            checkedComment++;
        }
        getReplyExist();
        return true;
    }
    function viewTypeFn(viewType,val){
        if(viewType[1]=="watch") viewURL="/ytd-watch-flexy/div[5]/div[1]/div";
        if(viewType[1]=="post") viewURL="/ytd-browse/ytd-two-column-browse-results-renderer/div[1]/ytd-section-list-renderer/div[2]";
        if(viewType[3]=="community"&&val!="") viewURL="/ytd-browse/ytd-two-column-browse-results-renderer/div[1]/ytd-section-list-renderer/div[2]";
        threadPath="/html/body/ytd-app/div/ytd-page-manager"+viewURL+"/ytd-comments/ytd-item-section-renderer/div[3]/";
        return viewURL;
    }
    function btnSettingFn(){
        btnSetting=getElementByXpath("/html/body/ytd-app/div/ytd-page-manager"+viewURL+"/ytd-comments/ytd-item-section-renderer/div[1]/ytd-comments-header-renderer/div[5]/ytd-comment-simplebox-renderer/yt-img-shadow/img");
        if(!btnSetting) return;
        var oBlackList = document.createElement("div");
        for(let i=0;i<list.length;i++){
            var banner = document.createElement('button');
            banner.onclick=function(){
                let id = list[i].id;
                let name = list[i].name;
                wordPure(name);
                Swal.fire({
                    title: '確定要將 '+name+' 從黑名單移除嗎?',
                    text: '頻道ID('+id+')',
                    icon: 'warning',
                    showCancelButton: true,
                    confirmButtonColor: '#3085d6',
                    cancelButtonColor: '#d33',
                    confirmButtonText: '從黑名單移除',
                    cancelButtonText: '取消'
                }).then((result) => {
                    if (result.isConfirmed) {
                        Swal.fire(
                            '已將 '+name+' 從黑名單移除',
                            '頻道ID('+id+')',
                            'info'
                        ).then(() => {
                            list.splice(banlistSearch(id)-1,1);
                            //list.length = 0;
                            blacklist("save");
                        })
                    }
                })
            }
            banner.innerText=wordPure(list[i].name)+" ("+list[i].id+")";
            let br = document.createElement('br');
            oBlackList.append(banner);
            oBlackList.append(br);
        }
        if(list.length==0){
            banner = document.createElement('p');
            banner.innerText="沒有任何人被你列入黑名單";
            oBlackList.append(banner);
        }
        btnSetting.onclick=function(){
            Swal.fire({
                title: '黑名單',
                width: blacklistWidth,
                html: this.appendChild(oBlackList),
                confirmButtonText: '確認',
                showDenyButton: true,
                denyButtonText: '導出/導入',
                backdrop: 'rgba(0,0,0,0.6)'
            }).then((result) => {
                if (result.isDenied) {
                    Swal.fire({
                        title: '黑名單(導出/導入)',
                        icon: 'question',
                        confirmButtonText: '導入',
                        showDenyButton: true,
                        denyButtonText: '導出',
                        backdrop: 'rgba(0,0,0,0.6)'
                    }).then((result) => {
                        if (result.isConfirmed) {
                            importFn();
                        } else if (result.isDenied) {
                            exportFn(GM_getValue("list"),exportName+".json");
                        }
                    })
                }
            })
        }
    }
    function getReplyExist(){
        for(let i=0;i<checkedComment+1;i++){
            if(noReply[i]) continue;
            let oreplyCountMain=getElementByXpath(threadPath+"/ytd-comment-thread-renderer["+i+"]/div/ytd-comment-replies-renderer/div[1]/ytd-button-renderer[1]/a/tp-yt-paper-button/yt-formatted-string");
            let oreplyCount=getElementByXpath(threadPath+"/ytd-comment-thread-renderer["+i+"]/div/ytd-comment-replies-renderer/div[1]/ytd-button-renderer[1]/a/tp-yt-paper-button/yt-formatted-string/span[2]");
            if(!(oreplyCountMain&&!oreplyCount)){
                if(!oreplyCount) noReply[i]=true;
                if(!oreplyCount) continue;
                if(parseInt(oreplyCount.textContent)===replyCount[i]) noReply[i]=true;
                if(parseInt(oreplyCount.textContent)===replyCount[i]) continue;
            }else{
                if(replyCount[i]===1) noReply[i]=true;
                if(replyCount[i]===1) continue;
            }
            checkedReply=1;
            if(replyCount[i]>0&&replyCount[i]%10===0) checkedReply=replyCount[i]+1;
            while(getReply(i)){
                foldedData(i);
                checkedReply++;
            }
            replyCount[i]=checkedReply-1;
        }
    }
    function getReply(thread){
        replyPath=threadPath+"/ytd-comment-thread-renderer["+thread+"]/div/ytd-comment-replies-renderer/div[1]/div/div[1]/ytd-comment-renderer["+checkedReply+"]";
        reply=document.evaluate(replyPath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
        return reply;
    }
    function foldedData(thread){
        replyUserId = getElementByXpath(replyPath+"/div[2]/div[2]/div[1]/div[2]/h3/a").href.toString().replace(/https:\/\/www.youtube.com\/channel\//g,"");
        replyUserName = getElementByXpath(replyPath+"/div[2]/div[2]/div[1]/div[2]/h3/a/span");
        if(replyUserName) replyUserName = replyUserName.textContent.toString().replace(/\n              /g,"").replace(/\n            /g,"");
        replyValue = getElementByXpath(replyPath+"/div[2]/div[2]/ytd-expander/div/yt-formatted-string[2]");
        btnBlackList = getElementByXpath(replyPath);
        btnBlackList.setAttribute("data-commentID",replyUserId);
        btnBlackList.setAttribute("data-commentName",replyUserName);
        btnBlackList.setAttribute("data-commentSeq",thread);
        if(banlistSearch(replyUserId)||wordFilter(replyValue.textContent)) {
            if(reply&&deleteComment) reply.style.display = 'none';
            if(replyValue) {
                replyValue.innerText = deleteText;
                replyValue.setAttribute("style","font-style: italic;");
            }
        }
        btnBlackList.ondblclick= function(){
            banCheck(
                this.getAttribute('data-commentID'),
                this.getAttribute('data-commentName'),
                this.getAttribute('data-commentSeq'),
                true
            );
        }
        //console.log(checkedReply,replyUserName,"reply");
    }
    function getComment(){
        path = "/html/body/ytd-app/div/ytd-page-manager"+viewURL+"/ytd-comments/ytd-item-section-renderer/div[3]/ytd-comment-thread-renderer["+(checkedComment+1)+"]";
        comment = document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
        if(comment===precomment[(checkedComment+1)]) return false;
        precomment[(checkedComment+1)]=comment
        return comment;
    }
    function defData(){
        commentUserId = getElementByXpath(path+"/ytd-comment-renderer/div[2]/div[2]/div[1]/div[2]/h3/a").href.toString().replace(/https:\/\/www.youtube.com\/channel\//g,"");
        commentUserName = getElementByXpath(path+"/ytd-comment-renderer/div[2]/div[2]/div[1]/div[2]/h3/a/span");
        if(commentUserName) commentUserName = commentUserName.textContent.toString().replace(/\n              /g,"").replace(/\n            /g,"");
        commentValue = getElementByXpath(path+"/ytd-comment-renderer/div[2]/div[2]/ytd-expander/div/yt-formatted-string[2]");
        btnBlackList = getElementByXpath(path+"/ytd-comment-renderer");
        btnBlackList.setAttribute("data-commentID",commentUserId);
        btnBlackList.setAttribute("data-commentName",commentUserName);
        btnBlackList.setAttribute("data-commentSeq",checkedComment+1);
        if(banlistSearch(commentUserId)||wordFilter(commentValue.textContent)) {
            if(comment&&deleteComment) comment.style.display = 'none';
            if(commentValue) {
                commentValue.innerText = deleteText;
                commentValue.setAttribute("style","font-style: italic;");
            }
        }
        btnBlackList.ondblclick= function(){
            banCheck(
                this.getAttribute('data-commentID'),
                this.getAttribute('data-commentName'),
                this.getAttribute('data-commentSeq')
            );
        }
        //console.log(checkedComment,commentUserName);
    }
    function banCheck(id,name,seq,fold){

        thisPath="/html/body/ytd-app/div/ytd-page-manager"+viewURL+"/ytd-comments/ytd-item-section-renderer/div[3]/ytd-comment-thread-renderer["+seq+"]";
        if(!fold) text = getElementByXpath(thisPath+"/ytd-comment-renderer/div[2]/div[2]/ytd-expander/div/yt-formatted-string[2]");
        if(fold) text = false;
        wordPure(name);
        if(!banlistSearch(id)){
            Swal.fire({
                title: '確定要將 '+name+' 列入黑名單嗎?',
                text: '頻道ID('+id+')',
                icon: 'warning',
                showCancelButton: true,
                confirmButtonColor: '#3085d6',
                cancelButtonColor: '#d33',
                confirmButtonText: '列入黑名單',
                cancelButtonText: '取消'
            }).then((result) => {
                if (result.isConfirmed) {
                    Swal.fire(
                        '已將 '+name+' 列入黑名單',
                        '頻道ID('+id+')',
                        'info'
                    ).then(() => {
                        list.unshift({"id":id,"name":name});
                        //list.length = 0;
                        blacklist("save");
                        if(text) {
                            let info = document.createElement('span');
                            text.innerText ="";
                            while(text.length > 0) {
                                text.pop();
                            }
                            info.innerText = "留言將在之後屏蔽";
                            info.setAttribute("style","font-style: italic;color:red;");
                            text.append(info);
                        }else{
                            replyCount[seq]=0;
                            noReply[seq]=false;
                        }
                    })
                }
            })
        }else{
            Swal.fire({
                title: '確定要將 '+name+' 從黑名單移除嗎?',
                text: '頻道ID('+id+')',
                icon: 'warning',
                showCancelButton: true,
                confirmButtonColor: '#3085d6',
                cancelButtonColor: '#d33',
                confirmButtonText: '從黑名單移除',
                cancelButtonText: '取消'
            }).then((result) => {
                if (result.isConfirmed) {
                    Swal.fire(
                        '已將 '+name+' 從黑名單移除',
                        '頻道ID('+id+')',
                        'info'
                    ).then(() => {
                        list.splice(banlistSearch(id)-1,1);
                        //list.length = 0;
                        blacklist("save");
                        if(text) {
                            let info = document.createElement('span');
                            text.innerText ="";
                            while(text.length > 0) {
                                text.pop();
                            }
                            info.innerText = "此留言者已從黑名單解封,之後就能再次看見";
                            info.setAttribute("style","font-style: italic;color:orange;");
                            text.append(info);
                        }
                    })
                }
            })
        }
    }
    function blacklist(event){
        if(event=="save") return GM_setValue("list", JSON.stringify(list));
        return JSON.parse(GM_getValue("list"));
    }
    function banlistSearch(id){
        for(let i=0;i<list.length;i++){
            if(list[i].id==id) {
                return i+1;
                break;
            }
        }
    }
    function exportFn(content, filename) {
        let odownload = document.createElement("a");
        odownload.download = filename;
        odownload.style.display = "none";
        let jsonBlob = new Blob([encodeURI(content,"utf-8")], {type:"text/plain;charset=utf-8"});
        odownload.href = URL.createObjectURL(jsonBlob);
        document.body.appendChild(odownload);
        odownload.click();
        document.body.removeChild(odownload);
    }
    function importFn(){
        let oimport = document.createElement("input");
        oimport.style.display = "none";
        oimport.type="file";
        oimport.accept=".json";
        oimport.onchange = function(){
            if(oimport.files.length != 0 && oimport.files[0].type.match(/json.*/)) {
                let reader = new FileReader();
                reader.onload = function(e) {
                    let loadData = JSON.parse(e.target.result);
                    Swal.fire({
                        title: '導入黑名單',
                        text: "請選擇要對新資料的處理方式",
                        confirmButtonText: '和原資料合併',
                        showDenyButton: true,
                        denyButtonText: '覆蓋原資料',
                        backdrop: 'rgba(0,0,0,0.6)'
                    }).then((result) => {
                        if (result.isDenied) {
                            list=loadData;
                            blacklist("save");
                            Swal.fire('已覆蓋原資料');
                        }else if (result.isConfirmed){
                            for(let i=0;i<loadData.length;i++){
                                if(!list.find(element => element.id === loadData[i].id)){
                                    //console.log(loadData[i]);
                                    list.unshift(loadData[i]);
                                }
                            }
                            blacklist("save");
                            Swal.fire('已合併兩資料');
                        }
                    })
                    reader.onerror = function(e) {
                        Swal.fire('無法讀取檔案');
                    }
                }
                reader.readAsText(oimport.files[0], "ISO-8859-1");
            } else {
                Swal.fire('上傳的檔案非json檔');
            }
        }
        oimport.click();
    }
    function wordFilter(commentText){
        if(commentMinLength>0&&commentText.length<commentMinLength) return commentText.length;
        if(commentMaxLength>0&&commentText.length>commentMaxLength) return commentText.length;
        if(banWords.length<=0) return false;
        for(let i=0;i<banWords.length;i++){
            if(commentText.indexOf(banWords[i])+1) return banWords[i];
        }
    }
    function wordPure(word){
        if(nameLength>=0 && word.length>nameLength){
            return word.substring(0,nameLength)+"...";
        }else{
            return word;
        }
    }
    function getElementByXpath(paths) {
        return document.evaluate(paths, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
    }
})();