原神/明日方舟/王者荣耀玩家指示器(可扩展/全平台) 三相之力指示器

在B站视频,动态评论区标注原神/明日方舟/王者荣耀玩家,可在配置里添加其它玩家以及修改匹配规则(动态,关注列表,视频),一键开启自动查询

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        原神/明日方舟/王者荣耀玩家指示器(可扩展/全平台) 三相之力指示器
// @namespace   Violentmonkey Scripts
// @match       https://*.bilibili.com/*
// @grant       none
// @version     1.16
// @description  在B站视频,动态评论区标注原神/明日方舟/王者荣耀玩家,可在配置里添加其它玩家以及修改匹配规则(动态,关注列表,视频),一键开启自动查询
// @author      fyb
// @license MIT
// ==/UserScript==
(function () {
    const setting = {
        automatic: 0,//是否开启自动查询,开启后自动在用户名后显示成分,关闭后需要点击用户名后的按钮查询(0为关闭,1为开启)
        matchingDynamic: 1, //在动态里查询关键词(0为关闭,1为开启) 可与其他兼容 仅能查询最近12条动态
        dynamicCount: 3, //显示包含关键词的动态个数,需要先开启matchingDynamic(0为关闭,1为仅显示条数,2为仅显示等级,3为显示条数和等级)
        matchingVideo: 1, //在视频标题简介里匹配关键词(0为关闭,1为开启)可与其他兼容
        matchingFollow: 1, //在关注列表里匹配关键词(0为关闭,1为开启)可与其他兼容
        matchingFollowPage: 2, //查询关注列表页数,一页50个,最多查询五页(1-5),需先开启matchingFollow
        noTagName: 1 //是否开启普通用户标签显示,只影响自动查询下的显示方式,手动查询该项永久为开启状态(0为关闭,1为开启)
    }
    const noTagName = { //没有被匹配到的用户的标签
        name: '普通用户',
        backgroundColor: '#9CA3AF'
    }
    const level = ['普通', '稀有', '史诗', '传说'] //仅当dynamicCount值为2或3时生效
    const levelRules = [0, 1, 3, 6]//最近动态数0-1 匹配普通,1-3 匹配稀有,3-6匹配史诗,大于6匹配传说  (小于等于规则)
    const match = [ //匹配规则,name为用户标签,color为显示标签的颜色(支持16进制颜色码#fb7299),keyword为匹配关键词数组,follows为匹配关注列表数组
        {
            name: '原神玩家',
            backgroundColor: "#8B5CF6",
            keyword: ['原神', '刻晴', '丘丘人', '雷电将军'],
            follows: ['原神']
        },
        {
            name: '明日方舟玩家',
            backgroundColor: "#F59E0B",
            keyword: ['明日方舟'],
            follows: ['明日方舟']
        },
        {
            name: '王者荣耀玩家',
            backgroundColor: "#60A5FA",
            keyword: ['王者荣耀'],
            follows: ['哔哩哔哩王者荣耀赛事']
        },
        {
            name: '一个魂',
            backgroundColor: "#10B981",
            keyword: ['嘉心糖', '顶碗人', '乃琳', '嘉然'],
            follows: ['嘉然今天吃什么', '乃琳Queen', '珈乐Carol', '贝拉kira', '向晚大魔王']
        }
    ]
    const hideFollow = {
        name: '隐藏关注',
        backgroundColor: '#9CA3AF'
    }

    let myCss = `
.userComponentBtn{
display:none;
border:1px solid #fb7299;
color:#fb7299;
cursor:default;
font-size:12px;
line-height:16px;
margin-left:5px;
}
.myCursor{
cursor:pointer;
}
.toHover:hover .userComponentBtn{
display:inline-block;
}
`
    let css = document.createElement("style");
    css.innerHTML = myCss;
    document.body.appendChild(css);
    const bili_new = document.getElementsByClassName('comment-m-v1').length + document.getElementsByClassName('item goback').length != 0;
    console.log('原神/明日方舟/王者荣耀玩家指示器(可扩展/全平台)插件加载成功')
    const bili_dyn_url = 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?host_uid='
    const bili_video_url = 'https://api.bilibili.com/x/space/arc/search?mid='
    const bili_follow_url = 'https://api.bilibili.com/x/relation/followings?ps=50&pn='
    const isAddBtn = new Set()
    const isRender = new Set()
    const userInfo = new Map()
    const bili_get_comment_list = () => {
        let lst = new Set()
        if (bili_new) {
            for (let c of document.getElementsByClassName('user-name')) {
                lst.add(c)
            }
            for (let c of document.getElementsByClassName('sub-user-name')) {
                lst.add(c)
            }

        } else {
            for (let c of document.getElementsByClassName('user')) {
                lst.add(c)
            }
        }
        return lst
    }
    const get_pid = (c) => {
        if (bili_new) {
            return c.dataset.userId
        } else {
            return c.children[0].href.replace(/[^\d]/g, "")
        }
    }
    const addTag = (c, m) => {
        let toAppend = document.createElement("DIV");
        toAppend.style.color = m.color || 'black';
        toAppend.style.display = 'inline-block'
        let innerText = m.name
        if (m.hasOwnProperty('count')) {
            if (setting.dynamicCount == 1 || setting.dynamicCount == 3) {
                innerText += '(' + m.count + '条)';
            }
            if (setting.dynamicCount == 2 || setting.dynamicCount == 3) {
                for (let i = 0; i < levelRules.length - 1; i++) {
                    if (m.count > levelRules[i] && m.count <= levelRules[i + 1]) {
                        innerText = level[i] + '&nbsp;|&nbsp;' + innerText;
                    }
                }
                if (m.count > levelRules[levelRules.length - 1]) {
                    innerText = level[level.length - 1] + '&nbsp;|&nbsp;' + innerText;
                }
            }

        }
        //toAppend.innerHTML = '[' + innerText + ']';
        toAppend.innerHTML = '<div style="background-color: ' + (m.backgroundColor || '#9CA3AF') + ';color: white;border-radius: 5px;padding: 3px 4px;margin-left: 3px;height: min-content;width: fit-content;font-size: smaller;display: inline;">' + innerText + '</div>';
        if (bili_new) {
            c.append(toAppend);
        } else {
            c.children[0].append(toAppend);
        }
    }
    const toMatchAll = (res, str) => {
        let matchStr = JSON.stringify(res[str])
        match.forEach(m => {
            for (let i = 0; i < m.keyword.length; i++) {
                if (matchStr.includes(m.keyword[i])) {
                    if (!res.type.has(m.name)) {
                        res.type.set(m.name, m)
                    }
                    break;
                }
            }
        })
    }
    const toMatchFollow = (res) => {
        let arr = res.follow
        if (arr.length == 0) {
            if (!res.type.has(hideFollow.name)) {
                res.type.set(hideFollow.name, hideFollow)
            }
        }
        else {
            match.forEach(m => {
                for (let i = 0; i < arr.length; i++) {
                    let a = m.follows.filter((v) => arr[i].uname == v)
                    if (a.length != 0) {
                        if (!res.type.has(m.name)) {
                            res.type.set(m.name, m)
                        }
                        break;
                    }
                }
            })
        }
    }
    const toMatchDynamic = (res) => {
        console.log(res)
        let dynArr = res.dynamic.data.cards || [];

        match.forEach(m => {
            let count = 0;
            for (let i = 0; i < dynArr.length; i++) {
                let a = m.keyword.filter((v) => JSON.stringify(dynArr[i].card).includes(v))
                if (dynArr[i].hasOwnProperty('orig')) {
                    // console.log('转发动态')
                    //a += m.keyword.filter((v) => JSON.stringify(dynArr[i].orig.modules.module_dynamic).includes(v))
                }
                if (a.length != 0) {
                    count++;
                }
            }
            if (count != 0) {
                let newObj = JSON.parse(JSON.stringify(m));
                newObj.count = count;
                if (!res.type.has(m.name)) {
                    res.type.set(m.name, newObj)
                }
            }
        })
    }
    const doAllMatch = (res) => {
        if (setting.matchingDynamic == 1 && setting.dynamicCount >= 1) {
            toMatchDynamic(res)
        }
        else {
            toMatchAll(res, 'dynamic')
        }
        toMatchAll(res, 'video')
        if (setting.matchingFollow == 1) {
            toMatchFollow(res)
        }

    }
    async function ajax(url) {
        const response = await window.fetch(url,{
            credentials: 'include'
        })
        return await response.json()
    }
    const config = {
        attributes: true,
        childList: true,
        subtree: true
    };
    var networkCount = 0;
    const getUserInfo = async (c) => {
        let result = {
            dynamic: {},
            video: {},
            follow: [],
            type: new Map()
        }
        let pid = get_pid(c);
        if (!userInfo.has(pid)) {
            if (bili_new) {
                userInfo.set(pid, {})
            }
            if (setting.matchingDynamic == 1) {
                result.dynamic = await ajax(bili_dyn_url + pid)
            }
            if (setting.matchingVideo == 1) {
                result.video = await ajax(bili_video_url + pid)
            }
            if (setting.matchingFollow == 1) {
                if (setting.matchingFollowPage >= 1 && setting.matchingFollowPage <= 5) {
                    for (let i = 1; i <= setting.matchingFollowPage; i++) {
                        let f = await ajax(bili_follow_url + i + '&vmid=' + pid)
                        if (f.data != null) {
                            result.follow = result.follow.concat(f.data.list)
                        }
                    }
                }
            }
            userInfo.set(pid, result)
            return result;
        }
        else {
            return userInfo.get(pid)
        }

    }

    const renderDOM = (c, res, isRenderNoTag) => {
        if ((res.type.size == 0 || (res.type.size == 1 && res.type.has(hideFollow.name))) && isRenderNoTag == 1) {
            addTag(c, noTagName)
        }
        res.type.forEach(m => {
            addTag(c, m)
        });
        isRender.add(c)
    }
    const addQueryBtn = (c) => {
        if (!isAddBtn.has(c)) {
            isAddBtn.add(c);
            let toAppend = document.createElement("DIV");
            toAppend.innerHTML = '查成分'
            toAppend.className = 'userComponentBtn myCursor'
            toAppend.addEventListener("click", function () {
                if (!isRender.has(c)) {
                    let _this = this;
                    _this.innerHTML = '查询中'
                    getUserInfo(c).then(function (res) {
                        doAllMatch(res)
                        renderDOM(c, res, 1)
                        _this.innerHTML = '查询完毕'
                        _this.className = 'userComponentBtn'
                    });
                }
            })
            if (bili_new) {
                c.parentNode.parentNode.className += ' toHover';
                c.parentNode.append(toAppend);
            } else {
                c.className += ' toHover';
                c.insertBefore(toAppend, c.children[1]);
            }
        }
    }
    var bili_match = ['comment-list ', 'reply-box']
    const callback = function (mutationsList, observer) {
        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                for (let q = 0; q < bili_match.length; q++) {
                    if (mutation.target.className.toString() == bili_match[q]) {
                        let bgcl = bili_get_comment_list()
                        if (setting.automatic == 0) {
                            bgcl.forEach(c => {
                                addQueryBtn(c);
                            })
                        };
                        if (setting.automatic == 1) {
                            bgcl.forEach(c => {
                                getUserInfo(c).then(function (res) {
                                    if (!isRender.has(c)) {
                                        if (JSON.stringify(res) == '{}') {
                                            return;
                                        }
                                        doAllMatch(res)
                                        renderDOM(c, res, setting.noTagName)
                                    }
                                });
                            });
                        }
                        break;
                    }
                }
            }
        }
    }




    const observer = new MutationObserver(callback);
    if (window.location.pathname.indexOf('video') != -1 || window.location.pathname.indexOf('read') != -1) {
        console.log("当前为视频页面")
        if (!bili_new) {
            observer.observe(document.body, config);
        } else {
            bili_match = ['reply-list', 'sub-reply-list'];
            observer.observe(document.body, config);
        }
    }
    if (window.location.hostname.indexOf('space') != -1 || window.location.hostname.indexOf('t.bilibili.com') != -1) {
        console.log("当前为动态页面")
        bili_match = ['comment-list has-limit', 'reply-box'];
        observer.observe(document.body, config);
    }
})();