Greasy Fork

Greasy Fork is available in English.

B站用户成分标签

B站评论区根据用户动态成分可视化添加标签(根据 新·三相之力指示器,原神指示器 修改制作)

目前为 2022-09-15 提交的版本。查看 最新版本

// ==UserScript==
// @name         B站用户成分标签
// @namespace    lycoris
// @version      1.4
// @description  B站评论区根据用户动态成分可视化添加标签(根据 新·三相之力指示器,原神指示器 修改制作)
// @author       Lemonades
// @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 keyword = [];


    const blog = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?&host_mid=';
    const is_new = document.getElementsByClassName('item goback').length != 0; // 检测是不是新版
    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;}
    .side-bar {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;}
    .side-bar:hover {color: #ffffff;background-color: #76cb9dc9;box-shadow: 2px 0px 4px 0px #76cb9d91;}
    .script-bar {display: none;position: fixed;z-index: 999;right: 65px;bottom: 20px;background-color: #ffffff;color: #929292;height: 240px;
    width: 200px;border-radius: 5px;box-shadow: 0px 0px 4px 2px #0000002b;padding: 10px;}
    .ActionBar {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;}
    #Exit {right: 0;}#Exit svg:hover path {fill: #f85c5cc9;}.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: #b1b1b154 0px 2px 3px 2px;}.btn:hover,#download-btn:hover {color: #76cb9dc9;}
    .input-tag {margin-bottom: 4px;width: 67%;left: 20%;border-radius: 4px;border: solid 1px rgb(100, 97, 97);}
    .input-tag:focus {outline: none;}.tag-label {font-size: 12px;display: inline-block;margin-right: 10px;}#input-tagcolor {width: 40px;height: 20px;}
    .tag-list {margin-top: 4px;width: 96%;height: 44%;bottom: 0px;border-radius: 4px;background-color: rgb(243, 238, 233);overflow-y: scroll;padding: 4px;}
    .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;}
    .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: 62px;height: 5px;outline: none;margin: 0 3px 0 3px;appearance: none;background: #b6b2b8;border-radius: 4px;}
    input[type="range"]::-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;}
    `;
    TAG_HTML.innerHTML = `
    <div class='side-bar'>&lt;</div>
    <div class='script-bar'>
        <div id="Exit" class="ActionBar">
            <svg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='1em' height='1em'>
                <path d='M783.8 892.7L512 620.9 240.2 892.7c-30 30-78.7 30-108.7 0s-30-78.7 0-108.7l271.8-271.8-271.9-271.9c-30-30-30-78.7 0-108.7s78.7-30 108.7 0L512 403.4l271.8-271.8c30-30 78.7-30 108.7 0s30 78.7 0 108.7L620.7 512.1 892.6 784c30 30 30 78.7 0 108.7s-78.7 30-108.8 0z' fill='#929292'></path>
            </svg>
        </div>
        <label class="tag-label" for="input-tagname">标签名称</label><input type="text" id="input-tagname" class="input-tag">
        <label class="tag-label" for="input-tagtext">标签内容</label><input type="text" id="input-tagtext" class="input-tag">
        <label class="tag-label" for="input-tagreg" title="多个关键词词用 | 分隔">标签正则</label><input type="text" id="input-tagreg" class="input-tag">
        <label class="tag-label" for="input-tagcolor">标签颜色</label><input type="color" name="" id="input-tagcolor">
        <label class="tag-label" style="margin-left: 10px; margin-right:8px" for="tag-hide">隐藏评论</label><input type="radio" id="tag-hide" style="margin:0;height:11px">
        <input type="button" class="btn" id="add-tag" value="添加标签" style='height: 20px;'>
        <label for="refresh-time" style="font-size: 12px;display: inline-block;">刷新间隔<input type="range" id="refresh-time" min="1" max="20" step="1" value="5"><span id="show-time">5s</span></label>
        <div class="tag-list">
        </div>
    </div>`;
    document.querySelector('head').appendChild(TAG_STYLE);
    document.querySelector('body').appendChild(TAG_HTML);
    let hideSet = false;
    const sideBar = document.querySelector('.side-bar');
    const scriptBar = document.querySelector('.script-bar');
    const exitBtn = document.querySelector('#Exit');
    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('.tag-list');
    let tag_hide = document.querySelector('#tag-hide');
    let refresh_time = document.querySelector("#refresh-time");
    sideBar.onclick = () => {
        scriptBar.style.display = 'block';
        sideBar.style.display = 'none';
    };
    exitBtn.onclick = () => {
        scriptBar.style.display = 'none';
        sideBar.style.display = 'block';
    };
    tag_hide.onclick = () => {
        hideSet = !hideSet;
        tag_hide.checked = hideSet;
    };
    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: hideSet
            });
        } else {
            alert('请将标签信息补充完整');
        }
    };
    refresh_time.onchange = () => {
        refreshTime = refresh_time.value * 1000;
        document.querySelector("#show-time").innerText = refresh_time.value + 's';
    };
    const addTag = (tag_dic) => {
        let tag_index = Object.keys(tag).length;
        tag[tag_index] = {
            tag: tag_dic.tag,
            text: tag_dic.text,
            reg: tag_dic.reg,
            color: tag_dic.color,
            hide: tag_dic.hide
        };
        tag_list.push(new Tag(tag[tag_index]));
        GM_setValue('tag', tag);
        let new_tag = document.createElement('div');
        new_tag.innerHTML = `<div class="delete-tag">x</div><p class="tag-info">${tag_dic.text}</p>`;
        new_tag.classList.add('tags');
        new_tag.style.width = tag_dic.text.length * 12 + 10 + 'px';
        new_tag.style.color = tag_dic.color;
        taglist.appendChild(new_tag);
        new_tag.children[0].onclick = () => {
            taglist.removeChild(new_tag);
            delete tag[tag_index];
            tag_list.pop(tag);
            GM_setValue('tag', tag);
        }
    };
    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.width = tag_dic.text.length * 10 + 5;
            this.tag_width = RegExp('[A-Za-z0-9]').test(this.tag) ? this.tag.length * 5 + 5 : this.tag.length * 10 + 5;
            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>`;
        };
        static hideComment(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;
        };
        check(pid, c) {
            if (this.list.has(pid)) {
                if (this.hide) {
                    Tag.hideComment(c).innerText = '评论已屏蔽';
                }
                if (!this.tagReg.test(c.textContent)) {
                    c.innerHTML += this.inner;
                }
                return true;
            } else if (this.nolist.has(pid)) {
                return true;
            }
        };
        detect(st, c, pid) {
            if (this.reg.test(st)) {
                if (this.hide) {
                    Tag.hideComment(c).innerText = '评论已屏蔽';
                }
                c.innerHTML += this.inner;
                this.list.add(pid);
            } else {
                this.nolist.add(pid);
            }
        };
    }
    class TagList {
        constructor() {
            this.list = [];
        }
        push(tag) {
            this.list.push(tag);
        }
        pop(tag) {
            this.list = [];
            let tag_key = Object.keys(tag);
            tag_key.map(key => this.list.push(new Tag(tag[key])));
        }
        check(pid, c) {
            for (let i of this.list) {
                let a = i.check(pid, c);
                if (a) {
                    return true;
                }
            }
        }
        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 IngredientDetection = () => {
        let commentlist = getCommentList();
        if (commentlist.length != 0) {
            commentlist.forEach(c => {
                if (keyword.length > 0) {
                    let comment = Tag.hideComment(c);
                    for (let reg of keyword) {
                        reg = new RegExp(reg);
                        if (reg.test(comment.innerText)) {
                            comment.innerText = '评论已屏蔽';
                            break;
                        }
                    }
                }
                let pid = getPid(c);
                let a = tag_list.check(pid, c);
                if (a) {
                    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);
                        }
                    },
                });
            });
        }
    };
    function throttle(func, wait) {
        let timeout,
            startTime = new Date();
        return function() {
            let context = this,
                args = arguments,
                curTime = new Date();
            clearTimeout(timeout);
            if (curTime - startTime >= refreshTime) {
                func.apply(context, args);
                startTime = curTime;
            } else {
                timeout = setTimeout(func, wait);
            }
        };
    };
    const wheel = () => {
        let time1 = new Date();
        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 tag = {};
    let refreshTime = 5000;
    const tag_store = GM_getValue('tag', {});
    let tag_list = new TagList();
    let tag_store_key = Object.keys(tag_store);
    tag_store_key.map(key => addTag(tag_store[key]));
    IngredientDetection();
    window.addEventListener("scroll", throttle(wheel, 1000));
})();