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

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

  1. // ==UserScript==
  2. // @name 原神/明日方舟/王者荣耀玩家指示器(可扩展/全平台) 三相之力指示器
  3. // @namespace Violentmonkey Scripts
  4. // @match https://*.bilibili.com/*
  5. // @grant none
  6. // @version 1.16
  7. // @description 在B站视频,动态评论区标注原神/明日方舟/王者荣耀玩家,可在配置里添加其它玩家以及修改匹配规则(动态,关注列表,视频),一键开启自动查询
  8. // @author fyb
  9. // @license MIT
  10. // ==/UserScript==
  11. (function () {
  12. const setting = {
  13. automatic: 0,//是否开启自动查询,开启后自动在用户名后显示成分,关闭后需要点击用户名后的按钮查询(0为关闭,1为开启)
  14. matchingDynamic: 1, //在动态里查询关键词(0为关闭,1为开启) 可与其他兼容 仅能查询最近12条动态
  15. dynamicCount: 3, //显示包含关键词的动态个数,需要先开启matchingDynamic(0为关闭,1为仅显示条数,2为仅显示等级,3为显示条数和等级)
  16. matchingVideo: 1, //在视频标题简介里匹配关键词(0为关闭,1为开启)可与其他兼容
  17. matchingFollow: 1, //在关注列表里匹配关键词(0为关闭,1为开启)可与其他兼容
  18. matchingFollowPage: 2, //查询关注列表页数,一页50个,最多查询五页(1-5),需先开启matchingFollow
  19. noTagName: 1 //是否开启普通用户标签显示,只影响自动查询下的显示方式,手动查询该项永久为开启状态(0为关闭,1为开启)
  20. }
  21. const noTagName = { //没有被匹配到的用户的标签
  22. name: '普通用户',
  23. backgroundColor: '#9CA3AF'
  24. }
  25. const level = ['普通', '稀有', '史诗', '传说'] //仅当dynamicCount值为2或3时生效
  26. const levelRules = [0, 1, 3, 6]//最近动态数0-1 匹配普通,1-3 匹配稀有,3-6匹配史诗,大于6匹配传说 (小于等于规则)
  27. const match = [ //匹配规则,name为用户标签,color为显示标签的颜色(支持16进制颜色码#fb7299),keyword为匹配关键词数组,follows为匹配关注列表数组
  28. {
  29. name: '原神玩家',
  30. backgroundColor: "#8B5CF6",
  31. keyword: ['原神', '刻晴', '丘丘人', '雷电将军'],
  32. follows: ['原神']
  33. },
  34. {
  35. name: '明日方舟玩家',
  36. backgroundColor: "#F59E0B",
  37. keyword: ['明日方舟'],
  38. follows: ['明日方舟']
  39. },
  40. {
  41. name: '王者荣耀玩家',
  42. backgroundColor: "#60A5FA",
  43. keyword: ['王者荣耀'],
  44. follows: ['哔哩哔哩王者荣耀赛事']
  45. },
  46. {
  47. name: '一个魂',
  48. backgroundColor: "#10B981",
  49. keyword: ['嘉心糖', '顶碗人', '乃琳', '嘉然'],
  50. follows: ['嘉然今天吃什么', '乃琳Queen', '珈乐Carol', '贝拉kira', '向晚大魔王']
  51. }
  52. ]
  53. const hideFollow = {
  54. name: '隐藏关注',
  55. backgroundColor: '#9CA3AF'
  56. }
  57.  
  58. let myCss = `
  59. .userComponentBtn{
  60. display:none;
  61. border:1px solid #fb7299;
  62. color:#fb7299;
  63. cursor:default;
  64. font-size:12px;
  65. line-height:16px;
  66. margin-left:5px;
  67. }
  68. .myCursor{
  69. cursor:pointer;
  70. }
  71. .toHover:hover .userComponentBtn{
  72. display:inline-block;
  73. }
  74. `
  75. let css = document.createElement("style");
  76. css.innerHTML = myCss;
  77. document.body.appendChild(css);
  78. const bili_new = document.getElementsByClassName('comment-m-v1').length + document.getElementsByClassName('item goback').length != 0;
  79. console.log('原神/明日方舟/王者荣耀玩家指示器(可扩展/全平台)插件加载成功')
  80. const bili_dyn_url = 'https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?host_uid='
  81. const bili_video_url = 'https://api.bilibili.com/x/space/arc/search?mid='
  82. const bili_follow_url = 'https://api.bilibili.com/x/relation/followings?ps=50&pn='
  83. const isAddBtn = new Set()
  84. const isRender = new Set()
  85. const userInfo = new Map()
  86. const bili_get_comment_list = () => {
  87. let lst = new Set()
  88. if (bili_new) {
  89. for (let c of document.getElementsByClassName('user-name')) {
  90. lst.add(c)
  91. }
  92. for (let c of document.getElementsByClassName('sub-user-name')) {
  93. lst.add(c)
  94. }
  95.  
  96. } else {
  97. for (let c of document.getElementsByClassName('user')) {
  98. lst.add(c)
  99. }
  100. }
  101. return lst
  102. }
  103. const get_pid = (c) => {
  104. if (bili_new) {
  105. return c.dataset.userId
  106. } else {
  107. return c.children[0].href.replace(/[^\d]/g, "")
  108. }
  109. }
  110. const addTag = (c, m) => {
  111. let toAppend = document.createElement("DIV");
  112. toAppend.style.color = m.color || 'black';
  113. toAppend.style.display = 'inline-block'
  114. let innerText = m.name
  115. if (m.hasOwnProperty('count')) {
  116. if (setting.dynamicCount == 1 || setting.dynamicCount == 3) {
  117. innerText += '(' + m.count + '条)';
  118. }
  119. if (setting.dynamicCount == 2 || setting.dynamicCount == 3) {
  120. for (let i = 0; i < levelRules.length - 1; i++) {
  121. if (m.count > levelRules[i] && m.count <= levelRules[i + 1]) {
  122. innerText = level[i] + '&nbsp;|&nbsp;' + innerText;
  123. }
  124. }
  125. if (m.count > levelRules[levelRules.length - 1]) {
  126. innerText = level[level.length - 1] + '&nbsp;|&nbsp;' + innerText;
  127. }
  128. }
  129.  
  130. }
  131. //toAppend.innerHTML = '[' + innerText + ']';
  132. 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>';
  133. if (bili_new) {
  134. c.append(toAppend);
  135. } else {
  136. c.children[0].append(toAppend);
  137. }
  138. }
  139. const toMatchAll = (res, str) => {
  140. let matchStr = JSON.stringify(res[str])
  141. match.forEach(m => {
  142. for (let i = 0; i < m.keyword.length; i++) {
  143. if (matchStr.includes(m.keyword[i])) {
  144. if (!res.type.has(m.name)) {
  145. res.type.set(m.name, m)
  146. }
  147. break;
  148. }
  149. }
  150. })
  151. }
  152. const toMatchFollow = (res) => {
  153. let arr = res.follow
  154. if (arr.length == 0) {
  155. if (!res.type.has(hideFollow.name)) {
  156. res.type.set(hideFollow.name, hideFollow)
  157. }
  158. }
  159. else {
  160. match.forEach(m => {
  161. for (let i = 0; i < arr.length; i++) {
  162. let a = m.follows.filter((v) => arr[i].uname == v)
  163. if (a.length != 0) {
  164. if (!res.type.has(m.name)) {
  165. res.type.set(m.name, m)
  166. }
  167. break;
  168. }
  169. }
  170. })
  171. }
  172. }
  173. const toMatchDynamic = (res) => {
  174. console.log(res)
  175. let dynArr = res.dynamic.data.cards || [];
  176.  
  177. match.forEach(m => {
  178. let count = 0;
  179. for (let i = 0; i < dynArr.length; i++) {
  180. let a = m.keyword.filter((v) => JSON.stringify(dynArr[i].card).includes(v))
  181. if (dynArr[i].hasOwnProperty('orig')) {
  182. // console.log('转发动态')
  183. //a += m.keyword.filter((v) => JSON.stringify(dynArr[i].orig.modules.module_dynamic).includes(v))
  184. }
  185. if (a.length != 0) {
  186. count++;
  187. }
  188. }
  189. if (count != 0) {
  190. let newObj = JSON.parse(JSON.stringify(m));
  191. newObj.count = count;
  192. if (!res.type.has(m.name)) {
  193. res.type.set(m.name, newObj)
  194. }
  195. }
  196. })
  197. }
  198. const doAllMatch = (res) => {
  199. if (setting.matchingDynamic == 1 && setting.dynamicCount >= 1) {
  200. toMatchDynamic(res)
  201. }
  202. else {
  203. toMatchAll(res, 'dynamic')
  204. }
  205. toMatchAll(res, 'video')
  206. if (setting.matchingFollow == 1) {
  207. toMatchFollow(res)
  208. }
  209.  
  210. }
  211. async function ajax(url) {
  212. const response = await window.fetch(url,{
  213. credentials: 'include'
  214. })
  215. return await response.json()
  216. }
  217. const config = {
  218. attributes: true,
  219. childList: true,
  220. subtree: true
  221. };
  222. var networkCount = 0;
  223. const getUserInfo = async (c) => {
  224. let result = {
  225. dynamic: {},
  226. video: {},
  227. follow: [],
  228. type: new Map()
  229. }
  230. let pid = get_pid(c);
  231. if (!userInfo.has(pid)) {
  232. if (bili_new) {
  233. userInfo.set(pid, {})
  234. }
  235. if (setting.matchingDynamic == 1) {
  236. result.dynamic = await ajax(bili_dyn_url + pid)
  237. }
  238. if (setting.matchingVideo == 1) {
  239. result.video = await ajax(bili_video_url + pid)
  240. }
  241. if (setting.matchingFollow == 1) {
  242. if (setting.matchingFollowPage >= 1 && setting.matchingFollowPage <= 5) {
  243. for (let i = 1; i <= setting.matchingFollowPage; i++) {
  244. let f = await ajax(bili_follow_url + i + '&vmid=' + pid)
  245. if (f.data != null) {
  246. result.follow = result.follow.concat(f.data.list)
  247. }
  248. }
  249. }
  250. }
  251. userInfo.set(pid, result)
  252. return result;
  253. }
  254. else {
  255. return userInfo.get(pid)
  256. }
  257.  
  258. }
  259.  
  260. const renderDOM = (c, res, isRenderNoTag) => {
  261. if ((res.type.size == 0 || (res.type.size == 1 && res.type.has(hideFollow.name))) && isRenderNoTag == 1) {
  262. addTag(c, noTagName)
  263. }
  264. res.type.forEach(m => {
  265. addTag(c, m)
  266. });
  267. isRender.add(c)
  268. }
  269. const addQueryBtn = (c) => {
  270. if (!isAddBtn.has(c)) {
  271. isAddBtn.add(c);
  272. let toAppend = document.createElement("DIV");
  273. toAppend.innerHTML = '查成分'
  274. toAppend.className = 'userComponentBtn myCursor'
  275. toAppend.addEventListener("click", function () {
  276. if (!isRender.has(c)) {
  277. let _this = this;
  278. _this.innerHTML = '查询中'
  279. getUserInfo(c).then(function (res) {
  280. doAllMatch(res)
  281. renderDOM(c, res, 1)
  282. _this.innerHTML = '查询完毕'
  283. _this.className = 'userComponentBtn'
  284. });
  285. }
  286. })
  287. if (bili_new) {
  288. c.parentNode.parentNode.className += ' toHover';
  289. c.parentNode.append(toAppend);
  290. } else {
  291. c.className += ' toHover';
  292. c.insertBefore(toAppend, c.children[1]);
  293. }
  294. }
  295. }
  296. var bili_match = ['comment-list ', 'reply-box']
  297. const callback = function (mutationsList, observer) {
  298. for (let mutation of mutationsList) {
  299. if (mutation.type === 'childList') {
  300. for (let q = 0; q < bili_match.length; q++) {
  301. if (mutation.target.className.toString() == bili_match[q]) {
  302. let bgcl = bili_get_comment_list()
  303. if (setting.automatic == 0) {
  304. bgcl.forEach(c => {
  305. addQueryBtn(c);
  306. })
  307. };
  308. if (setting.automatic == 1) {
  309. bgcl.forEach(c => {
  310. getUserInfo(c).then(function (res) {
  311. if (!isRender.has(c)) {
  312. if (JSON.stringify(res) == '{}') {
  313. return;
  314. }
  315. doAllMatch(res)
  316. renderDOM(c, res, setting.noTagName)
  317. }
  318. });
  319. });
  320. }
  321. break;
  322. }
  323. }
  324. }
  325. }
  326. }
  327.  
  328.  
  329.  
  330.  
  331. const observer = new MutationObserver(callback);
  332. if (window.location.pathname.indexOf('video') != -1 || window.location.pathname.indexOf('read') != -1) {
  333. console.log("当前为视频页面")
  334. if (!bili_new) {
  335. observer.observe(document.body, config);
  336. } else {
  337. bili_match = ['reply-list', 'sub-reply-list'];
  338. observer.observe(document.body, config);
  339. }
  340. }
  341. if (window.location.hostname.indexOf('space') != -1 || window.location.hostname.indexOf('t.bilibili.com') != -1) {
  342. console.log("当前为动态页面")
  343. bili_match = ['comment-list has-limit', 'reply-box'];
  344. observer.observe(document.body, config);
  345. }
  346. })();