// ==UserScript== // @name 仙家军成分查询Helper // @namespace www.bilibili.com // @version 1.5.9.2 // @description 用于标记仙家军和动态转发仙以及使用仙话术的b站用户。可能存在误伤,请注意辨别。脚本改自【糊狸-B站成分查询Helper】 // @author Darknights // @match https://*.bilibili.com/* // @match https://*.biligame.com/detail/?id=* // @exclude https://message.bilibili.com/* // @exclude https://manga.bilibili.com/* // @exclude https://www.bilibili.com/correspond/* // @exclude https://www.bilibili.com/page-proxy/* // @exclude https://live.bilibili.com/* // @exclude https://search.bilibili.com/* // @icon https://static.hdslb.com/images/favicon.ico // @connect bilibili.com // @connect biligame.com // @connect fastly.jsdelivr.net // @connect raw.githubusercontent.com // @grant GM_xmlhttpRequest // @grant GM_info // @license MIT // @run-at document-end // @downloadURL none // ==/UserScript== 'use strict'; /* 配置区 */ const config = { urlSource: 1, // 0:githubusercontent, 1:jsdelivr times: 2500, // 标签处理间隔时间 单位:ms testLog: 0, // 是否开启调试日志。0:不开启,1:开启 previewLength: 60, usingCheckProfile: 1, // 是否监测个人主页UID。0:不开启,1:开启 usingCheckComments: 1, // 是否监测评论区。0:不开启,1:开启 usingCheckRepos: 1, // 是否监测转发区。0:不开启,1:开启 usingCheckReferences: 1, // 是否监测动态被转发者。0:不开启,1:开启 usingCheckAts: 1, // 是否监测@他人。0:不开启,1:开启 usingCheckFollows: 1, // 是否监测关注/粉丝列表。0:不开启,1:开启 usingCheckGameComments: 1 // 是否监测游戏评价区。0:不开启,1:开启 } // 显示标签配置在👇面 let xianList; let xianFavList; let xianWordList; let xianLeakList; let ignoreList; let aidList; //以下为本地名单,可以自行添加,关键词列表均为字符串形式,注意要转义反斜杠 // 大部分为仙,少数可能有误判 const localXianList = []; // 转发者常见仙的,包含且不限于一些up主/被仙缠上的人等等 const localXianFavList = []; // 无视官号和无关号的动态,防止匹配到关键词浪费标签 const localIgnoreList = []; // 仙可能会用的词汇 const localXianWordList = []; // 被开盒者隐私信息,需要特殊处理故与关键词列表区分 const localXianLeakList = []; // 辅助,因为有些正则匹配返回值为空 const localAidList = []; const xianTag = ["仙", "#11DD77"]; const localXianTag = ["仙(本地)", "#11DD77"]; const xianRepostTag = ["转发仙:", "#1E971E"]; const localXianRepostTag = ["转发仙(本地):", "#1E971E"]; const favRepostTag = ["转发:", "#2C9EFF"]; const localFavRepostTag = ["转发(本地):", "#2C9EFF"]; const xianWordTag = ["命中:", "#04AEAB"]; const localXianWordTag = ["命中(本地):", "#04AEAB"]; const apiTag = ["出错,点此消除", "#FF3434"]; const refreshTag = ["然后点此🔄", "#FF7B00"]; const newVerTag = ["*已有新版本*", "#990CD0"]; const BLOG_URL = 'https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space?&host_mid='; const BILI_URL = 'https://t.bilibili.com/'; const SCRIPT_URL = 'https://greasyfork.org/zh-CN/scripts/467197'; const PRIVATE_TIPS = '*已隐藏,注意可能是无关话题被匹配*'; const XIAN_MATCH_TIPS = '*仙或其拥护者,可能存在误判,请注意辨别*'; const NEW_VERSION_TIPS = '*点击跳转安装页,更多功能尽在新版本*'; const CheckType = { Profile: 0, Comment: 1, At: 2, Reference: 3, Repo: 4, Follow: 5, GameComment: 6 } const recordMap = new Map(); const uidSet = new Set(); let updateTime; let onlineVersion; let isLatestVersion = false; const log = function (message) { return config.testLog ? console.log(message) : null; }; const spawnHtml = function (data, text) { return `<${data[0]}>`; } const spawnApiHtml = function (data) { return `<${data[0]}>`; } const spawnRefreshHtml = function (data) { return `<${data[0]}>`; } const spawnHtmlWithRef = function (data, word, link, text) { return `<${data[0]}${word}>`; } const getxianListUrl = function () { if (config.urlSource === 0) { return "https://raw.githubusercontent.com/Darknights1750/XianLists/main/xianLists.json"; } return "https://fastly.jsdelivr.net/gh/Darknights1750/XianLists@main/xianLists.json"; }; // 检测是不是新版 const isNew = function () { if (location.host === 'space.bilibili.com') { return true; } if (document.getElementsByClassName('item goback').length > 0) { return true; } if (document.getElementsByClassName('app-v1').length > 0) { return true; } if (document.getElementsByClassName('opus-detail').length > 0) { return true; } if (document.getElementsByClassName('bgc').length > 0) { return true; } return false; }; // 检测是不是游戏中心 const checkURL = function () { if (location.host === 'www.biligame.com') { config.usingCheckProfile = 0; config.usingCheckComments = 0; config.usingCheckRepos = 0; config.usingCheckReferences = 0; config.usingCheckAts = 0; config.usingCheckFollows = 0; } else { config.usingCheckGameComments = 0; if (location.host !== 'space.bilibili.com') { config.usingCheckProfile = 0; config.usingCheckFollows = 0; } } }; const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay)); const getXianListOnline = function () { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: getxianListUrl(), 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: res => { if (res.status === 200) { resolve(JSON.parse(res.responseText)); } else { resolve(JSON.parse('{"xianList":[],"xianFavList":[],"xianWordList":[]}')); } } }); }); } const createRefreshFun = function () { let e = document.createElement('script'); e.innerText = `const refreshTags=function(){Array.prototype.slice.call(document.getElementsByClassName('xian-fail')).forEach((item)=>{item.remove()})}`; document.head.appendChild(e); } const compareVersions = function (curVer, netArr) { var curArr = curVer.split('.'); var netArr = netArr.split('.'); for (var i = 0; i < Math.max(curArr.length, netArr.length); i++) { var curNum = parseInt(curArr[i] || 0); var netNum = parseInt(netArr[i] || 0); if (curNum < netNum) { return 1; } if (curNum > netNum + 10 || !/^\d+$/.test(curNum) || !/^\d+$/.test(netNum)) { return 2; } if (curNum > netNum) { return -1; } } return 0; } const fillLists = async function () { let json = await getXianListOnline(); xianList = json.xianList; xianFavList = json.xianFavList; ignoreList = [...localIgnoreList, ...json.ignoreList]; xianWordList = json.xianWordList.map((item) => new RegExp(item)); xianLeakList = json.xianLeakList.map((item) => new RegExp(item)); aidList = json.aidList.map((item) => new RegExp(item)); aidList = [...aidList, ...localAidList]; updateTime = json.updateTime; onlineVersion = json.version; } const runHelper = function () { /* Functions */ const isBlank = function (str) { if (!str || /^\s*$/.test(str)) { return true; } return false; } const getUid = function (htmlEntity, checkType) { if (checkType === CheckType.Profile) { return window.location.href.match(/(?<=space\.bilibili\.com\/)\d+/)[0]; } if (checkType === CheckType.Comment) { return isNew() ? htmlEntity.dataset.userId : htmlEntity.children[0].href.replace(/[^\d]/g, ""); } if (checkType === CheckType.Repo) { return htmlEntity._profile.uid; } if (checkType === CheckType.At) { return htmlEntity.dataset.oid ? htmlEntity.dataset.oid : htmlEntity.dataset.userId; } if (checkType === CheckType.Reference) { return htmlEntity._profile.uid; } if (checkType === CheckType.Follow) { return htmlEntity.parentElement.href.replace(/[^\d]/g, ""); } if (checkType === CheckType.GameComment) { return htmlEntity.href.replace(/[^\d]/g, ""); } } const getName = function (htmlEntity, checkType) { if (checkType === CheckType.Profile) { return htmlEntity.innerText; } if (checkType === CheckType.Comment) { return isNew() ? htmlEntity.innerText : htmlEntity.children[0].innerText; } if (checkType === CheckType.Repo) { return htmlEntity.innerText; } if (checkType === CheckType.At) { return htmlEntity.innerText.replace(/@/g, ""); } if (checkType === CheckType.Reference) { return htmlEntity.innerText; } if (checkType === CheckType.Follow) { return htmlEntity.innerText; } if (checkType === CheckType.GameComment) { return htmlEntity.innerText; } } const getCommentList = function () { if (isNew()) { const lst = new Set(); for (let c of document.getElementsByClassName('user-name')) { lst.add(c); } for (let c of document.getElementsByClassName('sub-user-name')) { lst.add(c); } return Array.from(lst); } else { return document.getElementsByClassName('user'); } } const getRepoList = function () { return document.getElementsByClassName('bili-dyn-forward-item__uname'); } const getReferenceList = function () { return document.getElementsByClassName('dyn-orig-author__name'); } const getFollowList = function () { return Array.from(document.getElementsByClassName('fans-name')); } const getGameCommentList = function () { return Array.from(document.querySelectorAll("a.user-name")); } const getAtList = function () { const lst = new Set(); for (let c of document.getElementsByClassName('jump-link user')) { lst.add(c); } for (let c of document.getElementsByClassName('bili-rich-text-module at')) { lst.add(c); } return Array.from(lst); } const spliceText = function (moduleDynamic) { let fullTextArr = []; if (moduleDynamic.topic != null && !isBlank(moduleDynamic.topic.name)) { fullTextArr.push(moduleDynamic.topic.name); } if (moduleDynamic.desc != null && !isBlank(moduleDynamic.desc.text)) { fullTextArr.push(moduleDynamic.desc.text); } if (moduleDynamic.major != null) { if (moduleDynamic.major.archive != null) { if (!isBlank(moduleDynamic.major.archive.title)) { fullTextArr.push(moduleDynamic.major.archive.title); } if (!isBlank(moduleDynamic.major.archive.desc)) { fullTextArr.push(moduleDynamic.major.archive.desc); } } if (moduleDynamic.major.article != null) { if (!isBlank(moduleDynamic.major.article.title)) { fullTextArr.push(moduleDynamic.major.article.title); } if (!isBlank(moduleDynamic.major.article.desc)) { fullTextArr.push(moduleDynamic.major.article.desc); } } if (moduleDynamic.major.live != null && !isBlank(moduleDynamic.major.live.title)) { fullTextArr.push(moduleDynamic.major.live.title); } } if (moduleDynamic.additional != null && moduleDynamic.additional.ugc != null && !isBlank(moduleDynamic.additional.ugc.title)) { fullTextArr.push(moduleDynamic.additional.ugc.title); } return fullTextArr.join('//'); } const previewText = function (text, index, len) { const left = Math.max(0, index - len); const right = Math.min(text.length, index + len); let textPart = ''; if (left > 0) { textPart += '...'; } textPart += text.substring(left, right).replace(/\n|\r/g, '').trim(); if (right < text.length) { textPart += '...'; } return textPart; } const findRepost = function (items, ownId, isFav, isLocal) { const usingList = isLocal ? (isFav ? localXianFavList : localXianList) : (isFav ? xianFavList : xianList); for (let i = 0; i < items.length; i++) { const item = items[i]; for (const key in item) { if (key === 'orig') { const origId = String(item.orig.modules.module_author.mid); if (origId === ownId) { return null; } if (usingList.indexOf(origId) > -1) { const origName = String(item.orig.modules.module_author.name); const ownFullText = spliceText(item.modules.module_dynamic); const origFullText = spliceText(item.orig.modules.module_dynamic); const ownTextPart = previewText(ownFullText, 0, config.previewLength); const origTextPart = previewText(origFullText, 0, config.previewLength); const bothTextPart = `${ownTextPart}//@${origName}:${origTextPart}`; const link = String(item.id_str); return [origId, origName, link, bothTextPart]; } } } } return null; } const hear = function (text, name, isLocal) { if (isBlank(text) || ignoreList.indexOf(name) >= 0) { return null; } const usingWordList = isLocal ? localXianWordList : xianWordList; const usingLeakList = isLocal ? localXianLeakList : xianLeakList; for (const word of usingWordList) { const matchRes = text.match(word); if (matchRes != null) { let matchStr = matchRes[0]; let matchIndex = matchRes.index; if (matchStr === '') { for (const aidWord of aidList) { const matchAid = text.match(aidWord); if (matchAid != null) { matchStr = matchAid[0]; matchIndex = matchAid.index; break; } } } matchStr = matchStr.replace(/\n|\r/g, '').trim(); return [matchStr, matchIndex + matchStr.length / 2]; } } for (const word of usingLeakList) { const matchRes = text.match(word); if (matchRes != null) { return ['可能是盒隐私', -1]; } } return null; } /** * 查找关键词 * @param {} items 动态列表 * @returns [关键词,动态id,动态片段] */ const findWord = function (items, ownName, isLocal) { for (let i = 0; i < items.length; i++) { const item = items[i]; let origName; let ownFullText = spliceText(item.modules.module_dynamic); let origFullText; let ownTextPart; let origTextPart; let returnWord; // 关键词 let returnPart; // 预览文本 for (const key in item) { if (key === 'orig') { origName = String(item.orig.modules.module_author.name); origFullText = spliceText(item.orig.modules.module_dynamic); break; } } const ownMatch = hear(ownFullText, ownName, isLocal); const origMatch = hear(origFullText, origName, isLocal); if (ownMatch == null && origMatch == null) { continue; } if (ownMatch == null) { returnWord = '🔁' + origMatch[0]; } else { returnWord = ownMatch[0]; } const ownIndex = ownMatch ? ownMatch[1] : 0; const origIndex = origMatch ? origMatch[1] : 0; ownTextPart = ownIndex < 0 ? PRIVATE_TIPS : previewText(ownFullText, ownIndex, config.previewLength / 2); returnPart = ownTextPart; if (!isBlank(origName)) { origTextPart = origIndex < 0 ? PRIVATE_TIPS : previewText(origFullText, origIndex, config.previewLength / 2); returnPart = ownTextPart + `//@${origName}:${origTextPart}`; } return [returnWord, String(item.id_str), returnPart]; } return null; } //检查记录 const findRecord = async function (uid, name) { let oldTag; if (recordMap.has(uid)) { oldTag = recordMap.get(uid); uidSet.delete(uid); if (oldTag) { log('>>Record:' + name + '@UID-' + uid + '>>find>>' + oldTag.replaceAll(/<\/?a.*?>/g, "").replaceAll(/></g, "、").replaceAll(/&.t;/g, "")); } } else if (uidSet.has(uid)) { await sleep(500); oldTag = findRecord(uid, name); } else { uidSet.add(uid); } return oldTag; } const checkEntity = async function (htmlEntity, checkType) { if (htmlEntity.innerHTML.indexOf(` { if (res.status === 200) { let newTag = ''; if (xianList.indexOf(uid) > -1) { log('>>Find Target:' + name + '@UID-' + uid + '>>' + checkType); newTag += spawnHtml(xianTag, XIAN_MATCH_TIPS); } if (localXianList.indexOf(uid) > -1) { log('>>Find Local Target:' + name + '@UID-' + uid + '>>' + checkType); newTag += spawnHtml(localXianTag, XIAN_MATCH_TIPS); } const dynamicJson = JSON.parse(res.response).data; if (dynamicJson) { if (dynamicJson.items) { const repostMatch = findRepost(dynamicJson.items, uid, false, false); if (repostMatch != null) { log('>>Find Repost:' + name + '@UID-' + uid + '>>repost>>' + repostMatch[1] + '@UID-' + repostMatch[0] + '>>' + checkType); newTag += spawnHtmlWithRef(xianRepostTag, repostMatch[1], BILI_URL + repostMatch[2], repostMatch[3]); } else { const localRepostMatch = findRepost(dynamicJson.items, uid, false, true); if (localRepostMatch != null) { log('>>Find Local Repost:' + name + '@UID-' + uid + '>>repost>>' + repostMatch[1] + '@UID-' + repostMatch[0] + '>>' + checkType); newTag += spawnHtmlWithRef(localXianRepostTag, repostMatch[1], BILI_URL + repostMatch[2], repostMatch[3]); } } const wordMatch = findWord(dynamicJson.items, name, false); if (wordMatch != null) { log('>>Find Word:' + name + '@UID-' + uid + '>>say>>' + wordMatch[0] + '>>' + checkType); let fixedText = wordMatch[0]; if (fixedText.length > 15) { fixedText = fixedText.slice(0, 12) + '...'; } newTag += spawnHtmlWithRef(xianWordTag, fixedText, BILI_URL + wordMatch[1], wordMatch[2]); } else { const localWordMatch = findWord(dynamicJson.items, name, true); if (localWordMatch != null) { log('>>Find Local Word:' + name + '@UID-' + uid + '>>say>>' + wordMatch[0] + '>>' + checkType); let fixedText = wordMatch[0]; if (fixedText.length > 15) { fixedText = fixedText.slice(0, 12) + '...'; } newTag += spawnHtmlWithRef(localXianWordTag, fixedText, BILI_URL + wordMatch[1], wordMatch[2]); } } const favRepostMatch = findRepost(dynamicJson.items, uid, true, false); if (favRepostMatch != null) { log('>>Find Fav:' + name + '@UID-' + uid + '>>repost>>' + favRepostMatch[1] + '@UID-' + favRepostMatch[0] + '>>' + checkType); newTag += spawnHtmlWithRef(favRepostTag, favRepostMatch[1], BILI_URL + favRepostMatch[2], favRepostMatch[3]); } else { const localFavRepostMatch = findRepost(dynamicJson.items, uid, true, true); if (localFavRepostMatch != null) { log('>>Find Local Fav:' + name + '@UID-' + uid + '>>repost>>' + favRepostMatch[1] + '@UID-' + favRepostMatch[0] + '>>' + checkType); newTag += spawnHtmlWithRef(localFavRepostTag, favRepostMatch[1], BILI_URL + favRepostMatch[2], favRepostMatch[3]); } } } htmlEntity.innerHTML += newTag; recordMap.set(uid, newTag); } else { xianSpan.className = 'xian-fail'; htmlEntity.innerHTML += spawnApiHtml(apiTag); htmlEntity.innerHTML += spawnRefreshHtml(refreshTag); uidSet.delete(uid); log('仙家军成分查询Helper get dynamic fail...'); log(htmlEntity); log(res); } } else { xianSpan.className = 'xian-fail'; log('仙家军成分查询Helper request fail...'); log(htmlEntity); log(res); } }, }); } } } const checkComments = function () { const commentlist = getCommentList(); if (commentlist != null && commentlist.length > 0) { commentlist.forEach(htmlEntity => { checkEntity(htmlEntity, CheckType.Comment); }); } } const checkRepos = function () { const repolist = getRepoList(); if (repolist != null && repolist.length > 0) { repolist.forEach(htmlEntity => { checkEntity(htmlEntity, CheckType.Repo); }); } } const checkAts = function () { const atList = getAtList(); if (atList != null && atList.length > 0) { atList.forEach(htmlEntity => { checkEntity(htmlEntity, CheckType.At); }); } } const checkReferences = function () { const referenceList = getReferenceList(); if (referenceList != null && referenceList.length > 0) { referenceList.forEach(htmlEntity => { checkEntity(htmlEntity, CheckType.Reference); }); } } const checkFollows = function () { const followList = getFollowList(); if (followList != null && followList.length > 0) { followList.forEach(htmlEntity => { checkEntity(htmlEntity, CheckType.Follow); }); } } const checkProfile = async function () { let htmlEntity = document.getElementById('h-name'); if (htmlEntity != null) { checkEntity(htmlEntity, CheckType.Profile); } } const checkGameComments = async function () { const gameCommentList = getGameCommentList(); if (gameCommentList != null && gameCommentList.length > 0) { gameCommentList.forEach(htmlEntity => { checkEntity(htmlEntity, CheckType.GameComment); }); } } log(`仙家军成分查询Helper,启动! >>isNew: ${isNew()} >>Loading: ${window.location.href} >>List update time: ${updateTime} `) setInterval(() => { if (config.usingCheckProfile) { checkProfile(); } if (config.usingCheckComments) { checkComments(); } if (config.usingCheckRepos) { checkRepos(); } if (config.usingCheckReferences) { checkReferences(); } if (config.usingCheckAts) { checkAts(); } if (config.usingCheckFollows) { checkFollows(); } if (config.usingCheckGameComments) { checkGameComments(); } }, config.times); } const start = async function () { checkURL(); createRefreshFun(); await fillLists(); runHelper(); } start();