【B站】评论区成分标签·关键词屏蔽

B站评论区可视化添加用户成分标签,用户评论关键词屏蔽

目前為 2022-09-26 提交的版本,檢視 最新版本

// ==UserScript==
// @name         【B站】评论区成分标签·关键词屏蔽
// @namespace    lycoris
// @version      1.7
// @description  B站评论区可视化添加用户成分标签,用户评论关键词屏蔽
// @author       Lyzoris
// @match        https://www.bilibili.com/video/*
// @icon         https://static.hdslb.com/images/favicon.ico
// @connect      bilibili.com
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @license MIT
// @run-at document-end
// ==/UserScript==
(function () {
    "use strict";
    const blog = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?&host_mid=';
    const is_new = document.getElementsByClassName('item goback').length != 0; // 检测B站版本
    const TAG_STYLE = document.createElement('style');
    const TAG_HTML = document.createElement('div');
    TAG_STYLE.innerHTML = `
    .userTag{display:inline-block;position:relative;text-align:center;border-width:0px;vertical-align:middle;margin-left:8px}
    .tag-text{border-color:rgba(169,195,233,0.1803921568627451);color:rgba(87,127,184,1);background-image:linear-gradient(90deg,rgba(158,186,232,0.2),rgba(158,186,232,0.2));float:left;text-align:center;height:12px;line-height:12px;border-width:0.5px;border-style:solid;border-bottom-left-radius:1px;border-top-left-radius:1px}
    .tag-font{width:200%;height:200%;font-weight:400;transform-origin:center;transform:scale(0.5) translate(-50%,-50%);font-size:20px;line-height:24px}.tag-name{position:relative;border-bottom-right-radius:1px;border-top-right-radius:1px;float:left;box-sizing:content-box;text-align:center;height:12px;line-height:12px;border-width:0.5px;border-color:#f25d8e;border-style:solid;border-bottom-left-radius:1px;border-top-left-radius:1px;color:#f25d8e}
    .tagbar-hide{position:fixed;right:10px;bottom:10px;width:20px;height:20px;font-size:15px;line-height:20px;text-align:center;color:#009688;background-color:#e2e1e2b3;border-radius:5px;box-shadow:2px 0px 4px 0px #0000002b}
    .tagbar-hide svg{width:100%;height:100%}.tagbar-hide:hover{background-color:#76cb9dc9;box-shadow:2px 0px 4px 0px #76cb9d91}.tagbar-hide:hover svg path{fill:#ffffff}.active{background-color:#ab85d1c9;box-shadow:2px 0px 4px 0px #c893e291}
    .active svg path{fill:#ffffff}.tagbar{display:none;position:fixed;z-index:999;right:65px;bottom:20px;background-color:#ffffff;color:#929292;height:400px;width:300px;border-radius:5px;box-shadow:0px 0px 4px 2px #0000002b;padding:10px}.tag-bar{width:100%;height:64%}
    .comment-bar{width:100%;height:36%}.tagbar-action{position:absolute;top:2px;width:15px;height:15px;line-height:15px;margin:0;border-radius:50%;background-color:#fff0;text-align:center;font-size:small;color:#929292}#tagbar-setting{right:0;top:0}
    #tagbar-setting svg:hover path{fill:#76b1ef}.tagbar-btn{background-color:#ffffff;color:#929292;width:60px;height:20px;margin:5px 0px 5px 0;border:none;border-radius:4px;font-weight:bold;transform-origin:center;transform:scale(0.9) translate(-10%,-10%);font-size:13px;text-align:center;box-shadow:#b1b1b13d 0px 0px 3px 2px}
    .tagbar-btn:hover{color:#76cb9dc9}.tagbar-btn:active{color:#b4b6ee}.input-tag{margin-bottom:4px;width:79%;left:20%;border-radius:4px;border:solid 1px #b0b0b0}.input-tag:focus{outline:none}.tagbar-label{font-size:12px;display:inline-block;margin-right:10px}#input-tagcolor{width:52px;height:20px;border-radius:4px;border:solid 1px #b0b0b0}
    .tags{display:inline-block;width:40px;height:20px;font-size:12px;border-radius:5px;color:#49414b;text-align:center;transform-origin:center;transform:scale(0.9) translate(-10%,-10%);line-height:20px;background-color:#bbc4cdd1;margin-left:3px;margin-top:5px;cursor:pointer}.tags:hover{background-color:#8dbbccc4}
    .delete-tag{position:relative;top:-3px;right:0;width:20px;height:20px;border-radius:50%;transform-origin:center;transform:scale(0.6) translate(-50%,-50%);font-size:20px;color:#837171;line-height:18px;background-color:#b2bfc67a}.delete-tag:hover{color:white;background-color:crimson}.delete-tag:active{color:white;background-color:#f0f}
    .tag-info{position:relative;margin-top:-20px;font-weight:bold}#refresh-time{width:80px;height:5px;outline:none;margin:0 3px 0 3px;appearance:none;background:#b6b2b8;border-radius:4px}#refresh-time::-webkit-slider-thumb{-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-box-shadow:0 0 2px;width:10px;height:10px;border-radius:50%;background-size:cover;background-color:#fff}
    .tagbar-taglist{margin-top:4px;width:98%;height:46%;bottom:0px;border-radius:4px;background-color:rgb(243,238,233);overflow-y:scroll;padding:4px}.tagbar-commentlist{margin-top:4px;width:98%;height:70%;bottom:0px;border-radius:4px;background-color:rgb(243,238,233);overflow-y:scroll;padding:4px}.tagbar-taglist::-webkit-scrollbar,.tagbar-commentlist::-webkit-scrollbar{width:4px;height:4px}
    .tagbar-taglist::-webkit-scrollbar-thumb,.tagbar-commentlist::-webkit-scrollbar-thumb{border-radius:5px;box-shadow:inset 0 0 5px rgba(0,0,0,0.2);background:rgba(0,0,0,0.2)}.tagbar-taglist::-webkit-scrollbar-track,.tagbar-commentlist::-webkit-scrollbar-track{box-shadow:inset 0 0 5px rgba(0,0,0,0.2);border-radius:0px;background:rgba(0,0,0,0.1)}#flash-time{float:right;margin-right:10px}
    `;
    TAG_HTML.innerHTML = `
    <div class='tagbar-hide'>
        <svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="16" height="16">
            <path d="M593.949942 770.425747c2.908236-4.233418 4.614088-9.380648 4.614088-14.892175s-1.705851-10.658757-4.614088-14.922874L431.084621 577.00041c-10.32209-10.324136-10.32209-27.042913 0-37.381375l158.601204-159.117974c5.376451-4.843308 8.771781-11.818163 8.771781-19.61371 0-14.602579-11.83249-26.433022-26.434046-26.433022-5.953595 0-11.420097 1.979074-15.835663 5.298679l-5.300726 5.299703L375.020744 520.936533c-20.650319 20.647249-20.650319 54.114478 0 74.763774l177.556928 177.590698 1.811252 1.794879c4.690836 4.264117 10.903328 6.884804 17.740036 6.884804C581.18829 781.968641 589.183382 777.399579 593.949942 770.425747z" fill="#009688"></path>
        </svg>
    </div>
    <div class='tagbar'>
        <div id='tagbar-setting' class="tagbar-action">
            <svg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='10' height='10'>
                <path d='M1008.535135 710.670332l-70.446388 116.386904a112.099975 112.099975 0 0 1-140.572857 43.829042l-69.422645-17.083729c-5.502624 3.1992-11.133217 6.3984-16.763809 9.341665l-18.299425 62.896276C687.335435 981.514621 639.4754 1023.744064 582.657604 1023.744064H441.764828c-56.817796 0-104.677831-42.293427-110.308423-97.703574l-18.299425-62.896276a422.166458 422.166458 0 0 1-16.827793-9.341665l-69.614597
                17.083729a112.163959 112.163959 0 0 1-140.508873-43.893026L15.631361 710.542364a107.493127 107.493127 0 0 1 32.43989-144.731817L96.251206 518.910272a410.649338 410.649338 0 0 1 0-14.204448l-48.179955-46.836291a107.365159 107.365159 0 0 1-32.375906-144.731817l70.190452-116.450888a112.099975 112.099975 0 0 1 140.572857-43.893026l69.550612 17.083729c5.566608-3.1992 11.133217-6.3984 16.827794-9.341665l18.299425-62.896276C336.831061
                42.229443 384.691096 0 441.508892 0H582.529636c56.817796 0 104.677831 42.293427 110.372407 97.63959l18.235441 62.704324c5.75856 3.007248 11.325169 6.142464 16.827793 9.341665l69.678581-17.083729a112.163959 112.163959 0 0 1 140.508873 43.893026l70.446388 116.514872a107.493127 107.493127 0 0 1-32.375906 144.731817l-48.179955 46.772307a414.616346 414.616346 0 0 1 0 14.204448l48.243939 46.836291c47.092227 31.928018 62.192452 94.952262
                32.247938 145.115721z m-213.066733-142.23644l3.391152-31.032242a246.210447 246.210447 0 0 0 0-51.699076l-3.263184-30.904274 93.992502-91.305173-53.554612-88.553862-131.615096 32.311922-23.546113-16.059985a294.134466 294.134466 0 0 0-48.627844-26.809297l-27.513121-11.901025L568.453156 127.968008H455.649356L419.370426 252.736816l-27.705074 11.965009c-16.635841 7.166208-32.951762 16.187953-48.499875 26.745313l-23.610097 15.996001-131.487128-32.247938-53.36266 88.489878 94.056486 91.433141-3.263184 30.968258a247.874031 247.874031
                0 0 0 0 51.699076l3.1992 30.904274-93.928518 91.369157 53.554612 88.489878 131.551112-32.311922 23.610097 16.059985c15.292177 10.429393 31.544114 19.451137 48.563859 26.809297l27.577106 11.965009L455.905292 895.776056h112.611848l36.342914-124.768808 27.64109-11.965009c16.699825-7.166208 32.951762-16.187953 48.563859-26.745313l23.546113-15.996001 131.423144 32.247938 53.68258-88.745814-94.184454-91.369157zM512.147232 615.846038c57.329668 0
                103.846038-46.516371 103.974006-103.974006 0-57.329668-46.644339-103.974006-103.974006-103.974007S408.173225 454.542364 408.173225 511.872032 454.817564 615.846038 512.147232 615.846038z m0 127.968008c-127.968008 0-231.942014-103.910022-231.942015-231.942014 0-127.968008 103.910022-231.942014 231.942015-231.942015 127.968008 0 231.942014 103.910022 231.942014 232.133967A232.133967 232.133967 0 0 1 512.147232 743.814046z' fill='#929292'></path>
            </svg>
        </div>
        <div class="tag-bar">
            <label class="tagbar-label" for="input-tagname" title="标签的分类,比如游戏、Vtuber、主播、UP主等等">标签分类</label><input type="text" id="input-tagname" class="input-tag" autocomplete="off">
            <label class="tagbar-label" for="input-tagtext" title="标签具体内容,用以区别每个标签">标签内容</label><input type="text" id="input-tagtext" class="input-tag" autocomplete="off">
            <label class="tagbar-label" for="input-tagreg" title="匹配用户标签的正则关键词,多个关键词间用 | 分隔 【正则教程】https://www.runoob.com/regexp/regexp-tutorial.html">
            标签正则</label><input type="text" id="input-tagreg" class="input-tag" autocomplete="off">
            <label class="tagbar-label" for="input-tagcolor" title="标签文字颜色,可在面板中预览">标签颜色</label><input type="color" name="" id="input-tagcolor">
            <label class="tagbar-label" style="margin-left: 34px;" for="tag-hide">屏蔽该标签用户评论<input type="checkbox" id="tag-hide" style="margin-left:20px;height:11px"></label>
            <input type="button" class="tagbar-btn" id="add-tag" value="添加标签">
            <label for="refresh-time" style="float:right;margin-top: 5px;margin-right:6px;font-size: 12px;display: inline-block;" title="页面滚动时脚本刷新间隔时间,5-10s为宜">刷新间隔<input type="range" id="refresh-time" min="2" max="20" step="1" value="5"><span id="show-time">5s</span></label>
            <div class="tagbar-taglist"></div>
        </div>
        <div class="comment-bar">
            <label class="tagbar-label" for="input-comment-reg" title="建议同类关键词作为一个标签添加,多个词之间用 | 分隔">评论屏蔽正则</label><input type="text" id="input-comment-reg" class="input-tag" autocomplete="off" style="width:45%;">
            <input type="button" class="tagbar-btn" id="add-comment-reg" style="margin-top: 5px;margin-left: 15px;" value="添加规则">
            <div class="tagbar-commentlist"></div>
        </div>
    </div>`;
    const Head = document.querySelector('head');
    const Body = document.querySelector('body');
    Head.appendChild(TAG_STYLE);
    Body.appendChild(TAG_HTML);
    const sideBar = document.querySelector('.tagbar-hide');
    const tagBar = document.querySelector('.tagbar');
    let tag_name = document.querySelector('#input-tagname');
    let tag_text = document.querySelector('#input-tagtext');
    let tag_reg = document.querySelector('#input-tagreg');
    let tag_color = document.querySelector('#input-tagcolor');
    let add_tag_btn = document.querySelector('#add-tag');
    let taglist = document.querySelector('.tagbar-taglist');
    let tag_hide = document.querySelector('#tag-hide');
    let refresh_time = document.querySelector("#refresh-time");
    let add_tag_reg = document.querySelector('#add-comment-reg');
    let comment_reg = document.querySelector('#input-comment-reg');
    let commentlist = document.querySelector('.tagbar-commentlist');
    sideBar.onclick = () => {
        if (tagBar.style.display === 'block') {
            tagBar.style.display = 'none';
            sideBar.classList.remove('active');
        }else {
            tagBar.style.display = 'block';
            sideBar.classList.add('active');
        }
    };
    add_tag_btn.onclick = () => {
        if (tag_name.value && tag_text.value && tag_reg.value) {
            addTag({
                tag: tag_name.value,
                text: tag_text.value,
                reg: tag_reg.value,
                color: tag_color.value,
                hide: tag_hide.checked
            });
            tag_name.value = '';
            tag_text.value = '';
            tag_reg.value = '';
            tag_hide.checked = false;
        }else {
            alert('请将标签信息补充完整');
        }
    };
    refresh_time.onchange = () => {
        refreshTime = Number(refresh_time.value) * 1000;
        document.querySelector("#show-time").innerText = refresh_time.value + 's';
    };
    add_tag_reg.onclick = () => {
        if (comment_reg.value) {
            addKeyWord(comment_reg.value);
            comment_reg.value = '';
        }else {
            alert('请将关键词正则信息补充完整');
        };
    };
    const addKeyWord = (reg_text) => {
        if (!keyword.includes(reg_text)) {
            let new_tag = insertTag(commentlist, reg_text);
            let keyword_index = Object.keys(comment_keyword).length;
            comment_keyword[keyword_index] = reg_text;
            keyword.push(reg_text);
            GM_setValue('keyword', keyword);
            let deleteTag = () => {
                commentlist.removeChild(new_tag);
                delete comment_keyword[keyword_index];
                keyword = [];
                let keyword_index_new = Object.keys(comment_keyword);
                keyword_index_new.map(key => keyword.push(comment_keyword[key]));
                GM_setValue('keyword', keyword);
            };
            new_tag.children[0].onclick = deleteTag;
            new_tag.children[1].ondblclick = () => {
                comment_reg.value = reg_text;
                deleteTag();
            };
        }
    };
    const addTag = (tag_dic) => {
        let new_tag = insertTag(taglist, tag_dic.text, tag_dic.color);
        let tag_index = Object.keys(tag).length;
        tag[tag_index] = tag_dic;
        tag_list.push(tag, tag_index);
        let deleteTag = () => {
            taglist.removeChild(new_tag);
            tag_list.pop(tag, tag_index);
        };
        new_tag.children[0].onclick = deleteTag;
        new_tag.children[1].ondblclick = () => {
            tag_name.value = tag_dic.tag;
            tag_text.value = tag_dic.text;
            tag_reg.value = tag_dic.reg;
            tag_color.value = tag_dic.color;
            tag_hide.checked = tag_dic.hide;
            deleteTag();
        };
    };
    const insertTag = (parentNode, text, color = "#49414b") => {
        let new_tag = document.createElement('div');
        new_tag.innerHTML = `<div class="delete-tag">x</div><p class="tag-info">${text}</p>`;
        new_tag.classList.add('tags');
        new_tag.style.width = measureTextWidth("12px", text) + 8 + 'px';
        new_tag.style.color = color;
        parentNode.appendChild(new_tag);
        return new_tag;
    };
    const measureTextWidth = (fontSize, text) => {
        let fontFamily = "PingFang SC, HarmonyOS_Regular, Helvetica Neue, Microsoft YaHei, sans-serif";
        let canvas = document.createElement("canvas");
        let context = canvas.getContext("2d");
        context.font = fontSize + " " + fontFamily;
        let result = context.measureText(text);
        return Math.ceil(result.width);
    };
    class Tag {
        constructor(tag_dic) {
            this.tag = tag_dic.tag;
            this.text = tag_dic.text;
            this.tagReg = new RegExp(tag_dic.text);
            this.reg = new RegExp(tag_dic.reg);
            this.hide = tag_dic.hide;
            this.color = tag_dic.color;
            this.tag_width = measureTextWidth("12px", this.tag) + 2;
            this.width = measureTextWidth("12px", this.text) + 2;
            this.list = new Set();
            this.nolist = new Set();
            this.inner = `<div class='userTag'><div class='tag-name' style='border-color:#8da8e8;color:#5e80c4; width:${this.tag_width}px'><div class='tag-font'>${this.tag}</div></div>
            <div class='tag-text' style='color: ${this.color}; width:${this.width}px;'><div class='tag-font'>${this.text}</div></div></div>`;
        };
        check(pid, c) {
            if (this.list.has(pid)) {
                if (this.hide) {
                    getCommentTextNode(c).innerText = '评论已屏蔽';
                }
                if (!this.tagReg.test(c.textContent)) {
                    c.innerHTML += this.inner;
                }
                CHECK = true;
            }else if (this.nolist.has(pid)) {
                CHECK = true;
            }
        };
        detect(st, c, pid) {
            if (!this.list.has(pid) && !this.nolist.has(pid)) {
                if (this.reg.test(st)) {
                    if (this.hide) {
                        getCommentTextNode(c).innerText = '评论已屏蔽';
                    }
                    if (!this.tagReg.test(c.textContent)) {
                        c.innerHTML += this.inner;
                        this.list.add(pid);
                    }
                }else {
                    this.nolist.add(pid);
                }
            }
        };
    }
    class TagList {
        constructor() {
            this.list = [];
        }
        push(tag, index) {
            this.list.push(new Tag(tag[index]));
            GM_setValue('tag', tag);
        }
        pop(tag, index) {
            delete tag[index];
            this.list = [];
            let tag_key = Object.keys(tag);
            tag_key.map(key => this.list.push(new Tag(tag[key])));
            GM_setValue('tag', tag);
        }
        check(pid, c) {
            CHECK = false;
            if (keyword.length > 0) {
                let comment = getCommentTextNode(c);
                for (let reg of keyword) {
                    let Reg = new RegExp(reg);
                    if (Reg.test(comment.innerText)) {
                        comment.innerText = '评论已屏蔽';
                        break;
                    }
                }
            }
            this.list.map(i => i.check(pid, c));
        }
        detect(st, c, pid) {
            this.list.map(i => i.detect(st, c, pid));
        }
    }
    const getPid = (c) => {
        if (is_new) {
            return c.dataset.userId;
        }else {
            return c.querySelector('.name').getAttribute('data-usercard-mid') || c.children[0].href.replace(/[^\d]/g, "");
        }
    };
    const getCommentList = () => {
        if (is_new) {
            return document.querySelectorAll('.user-name,.sub-user-name');
        }else {
            return document.querySelectorAll('.user');
        }
    };
    const getCommentTextNode = (c) => {
        let comment;
        if (is_new) {
            if (c.classList.contains('user-name')) {
                comment = c.parentElement.nextSibling.children[0];
            }else {
                comment = c.parentElement.nextSibling;
            }
        }else {
            if (c.querySelector('.text-con')) {
                comment = c.querySelector('.text-con');
            }else {
                comment = c.nextSibling;
            }
        }
        return comment;
    };
    const IngredientDetection = () => {
        let commentlist = getCommentList();
        if (commentlist.length != 0) {
            commentlist.forEach(c => {
                let pid = getPid(c);
                tag_list.check(pid, c);
                if (CHECK) {
                    return;
                }
                let blogurl = blog + pid;
                GM_xmlhttpRequest({
                    method: "get",
                    url: blogurl,
                    data: '',
                    headers: {
                        'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36'
                    },
                    onload: function (res) {
                        if (res.status === 200) {
                            let st = JSON.stringify(JSON.parse(res.response).data);
                            tag_list.detect(st, c, pid);
                        }else {
                            console.log('加载用户信息失败');
                            console.log(res);
                        }
                    },
                });
            });
        }
    };
    const throttle = (func, wait) => {
        let timeout, startTime = new Date();
        return function () {
            let context = this, args = arguments, curTime = new Date();
            clearTimeout(timeout);
            if (Number(curTime) - Number(startTime) >= refreshTime) {
                func.apply(context, args);
                startTime = curTime;
            }else {
                timeout = setTimeout(func, wait);
            }
        };
    };
    const wheel = () => {
        let btns = document.querySelectorAll('.btn-more,.paging-box,.view-more-pagination,.view-more-btn');
        for (let btn of btns) {
            btn.onclick = () => {
                setTimeout(() => {
                    IngredientDetection();
                }, 500);
            };
        }
        IngredientDetection();
    };
    let CHECK = false;
    let tag = {};
    let comment_keyword = {};
    let keyword = [];
    let refreshTime = 5000;
    const key_storage = GM_getValue('keyword', []);
    key_storage.map(key => addKeyWord(key));
    let tag_list = new TagList();
    const tag_storage = GM_getValue('tag', {});
    let tag_storage_key = Object.keys(tag_storage);
    tag_storage_key.map(tag => addTag(tag_storage[tag]));
    IngredientDetection();
    window.addEventListener("scroll", throttle(wheel, 2000));
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址