您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在B站视频,动态评论区标注原神/明日方舟/王者荣耀玩家,可在配置里添加其它玩家以及修改匹配规则(动态,关注列表,视频),一键开启自动查询
// ==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] + ' | ' + innerText; } } if (m.count > levelRules[levelRules.length - 1]) { innerText = level[level.length - 1] + ' | ' + 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); } })();