虎牙重复弹幕过滤

虎牙重复弹幕过滤, 过滤视频弹幕, 过滤聊天弹幕, 过滤表情弹幕, 放过自己弹幕

  1. // ==UserScript==
  2. // @name 虎牙重复弹幕过滤
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.5
  5. // @description 虎牙重复弹幕过滤, 过滤视频弹幕, 过滤聊天弹幕, 过滤表情弹幕, 放过自己弹幕
  6. // @author Mindfulness
  7. // @match https://www.huya.com/*
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @run-at document-end
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const HuyaFilter = {
  17. selfUserName: '',
  18. filterCount: 0,
  19. filterTopList: [],
  20. recordDanmuList: [],
  21. _options: {},
  22. getOptions: function(optionKey, defaultValue)
  23. {
  24. if(this._options[optionKey] == undefined)
  25. {
  26. this._options[optionKey] = GM_getValue('_huya_filter_' + optionKey, defaultValue);
  27. }
  28. return this._options[optionKey];
  29. },
  30. setOptions: function(optionKey, optionValue)
  31. {
  32. GM_setValue('_huya_filter_' + optionKey, optionValue);
  33. this._options[optionKey] = optionValue;
  34. },
  35. createUI: function()
  36. {
  37. let icon = document.createElement('i');
  38. icon.id = 'J-room-chat-filter';
  39. icon.className = 'room-chat-tool';
  40. icon.style = 'display: inline-block;width: 35px;height: 22px;cursor:pointer;text-align:center;margin-top: 1px;border: 1px solid #ff8a00;border-radius: 5px;background-color: #ff8a00;color: #fff;font-weight: bold;user-select:none;';
  41. icon.innerText = '过滤';
  42.  
  43. let toolsPannel = document.querySelector('#tipsOrchat .chat-room__ft .chat-room__ft__pannel .room-chat-tools');
  44. toolsPannel.appendChild(icon);
  45.  
  46. let tipsOrchatRect = document.querySelector('#tipsOrchat').getBoundingClientRect();
  47. let mainCol = document.querySelector('#main_col');
  48. let mainColRect = mainCol.getBoundingClientRect();
  49. let popup = document.createElement('div');
  50. popup.id = 'J-room-chat-filter-pannel';
  51. let popTop = tipsOrchatRect.top - mainColRect.top - 300 - 2;
  52. let popLeft = tipsOrchatRect.left - mainColRect.left;
  53. popup.style='border:1px solid #333;width:336px;height:300px;background-color:#eee;cursor:default;position:absolute;top:' + popTop + 'px;left:' + popLeft + 'px;z-index:100;display:none;';
  54.  
  55. let popupHeader = document.createElement('div');
  56. popupHeader.style='padding:10px;border-bottom:1px solid #888;';
  57.  
  58. let filterCounter = document.createElement('span');
  59. filterCounter.id = 'J-room-chat-filter-counter';
  60. filterCounter.style = 'color:#008;';
  61. filterCounter.innerText = '已过滤: 0';
  62. popupHeader.appendChild(filterCounter);
  63.  
  64. let lastFilterDanmu = document.createElement('span');
  65. lastFilterDanmu.id = 'J-room-chat-filter-last';
  66. lastFilterDanmu.style = 'margin-left:10px;padding:0 2px;display:inline-flex;overflow:hidden;max-width:200px;height:18px;color:#aaa;background-color:#ff8;';
  67. lastFilterDanmu.title = '最后过滤弹幕内容';
  68. popupHeader.appendChild(lastFilterDanmu);
  69.  
  70. let popupClose = document.createElement('a');
  71. popupClose.innerText = '关闭';
  72. popupClose.style='cursor:pointer;color:#800;padding-right:10px;position:absolute;right:0;';
  73. popupClose.addEventListener('click', () => {popup.style.display = 'none'});
  74. popupHeader.appendChild(popupClose);
  75. popup.appendChild(popupHeader);
  76.  
  77. let popupOptions = document.createElement('div');
  78. popupOptions.style='padding:10px;border-bottom:1px solid #888;';
  79. popup.appendChild(popupOptions);
  80.  
  81. let popupFilterTopList = document.createElement('div');
  82. popupFilterTopList.style='padding:10px;';
  83.  
  84. let topListElement = document.createElement('ul');
  85. topListElement.id = 'J-room-chat-filter-topList';
  86. topListElement.style = 'padding:0;margin:0;';
  87. popupFilterTopList.appendChild(topListElement);
  88.  
  89. let resetElement = document.createElement('a');
  90. resetElement.innerText = '重置统计';
  91. resetElement.style = 'cursor:pointer;color:#800;padding-right:10px;position:absolute;right:0;bottom:10px;';
  92. resetElement.addEventListener('click', function()
  93. {
  94. this.filterCount = 0;
  95. this.filterTopList = [];
  96. this.recordDanmuList = [];
  97. this.showFilterCountInfo();
  98. }.bind(this));
  99. popupFilterTopList.appendChild(resetElement);
  100.  
  101. popup.appendChild(popupFilterTopList);
  102.  
  103. mainCol.appendChild(popup);
  104.  
  105. icon.addEventListener('click', (e) => {popup.style.display = popup.style.display == 'block' ? 'none' : 'block'});
  106.  
  107. this.showCheckboxOptions(popupOptions, 'filter_video_danmu', '过滤视频弹幕');
  108. this.showCheckboxOptions(popupOptions, 'filter_chat_danmu', '过滤聊天弹幕');
  109. this.showCheckboxOptions(popupOptions, 'filter_icon_danmu', '过滤表情弹幕');
  110. this.showCheckboxOptions(popupOptions, 'filter_skip_me', '放过自己发的弹幕');
  111. },
  112.  
  113. showCheckboxOptions: function(contriner, optionKey, optionText)
  114. {
  115. let checkbox = document.createElement('input');
  116. checkbox.type = 'checkbox';
  117. checkbox.checked = this.getOptions(optionKey, 1) == 1;
  118.  
  119. checkbox.addEventListener('change', function(e)
  120. {
  121. this.setOptions(optionKey, checkbox.checked ? 1 : 0);
  122. console.log(optionText + ': ' + (checkbox.checked ? '已开启' : '已关闭'));
  123. }.bind(this));
  124.  
  125. let span = document.createElement('span');
  126. span.innerText = optionText;
  127. span.style='padding-left:10px;';
  128.  
  129. let label = document.createElement('label');
  130. label.style = 'display: inline-block;width:45%;';
  131. label.appendChild(checkbox);
  132. label.appendChild(span);
  133. contriner.appendChild(label);
  134. },
  135.  
  136. showFilterCountInfo: function()
  137. {
  138. document.querySelector('#J-room-chat-filter-counter').innerHTML = '已过滤: ' + this.filterCount;
  139.  
  140. let topListElement = document.querySelector('#J-room-chat-filter-topList');
  141. topListElement.innerHTML = '';
  142.  
  143. for(let i = 0; i < 10; i++)
  144. {
  145. let itemElement = document.createElement('li');
  146. let showIndex = i + 1;
  147. let itemHtml = '<span style="font-weight:bold;">' + showIndex + '.</span>';
  148. if(i < this.filterTopList.length) itemHtml += ' (' + this.filterTopList[i].count + ') ' + this.filterTopList[i].danmu;
  149. itemElement.innerHTML = itemHtml;
  150. itemElement.style = 'padding:0;margin:0;list-style:none;display:block;width:316px;overflow:hidden;height:18px;line-height:18px;';
  151. topListElement.appendChild(itemElement);
  152. }
  153. },
  154.  
  155. start: function()
  156. {
  157. let videoDanmuDiv = document.querySelector('#danmudiv');
  158. let chatDanmuDiv = document.querySelector('#chat-room__list');
  159.  
  160. if(!videoDanmuDiv || !chatDanmuDiv) return setTimeout(this.start.bind(this), 1000);
  161.  
  162. this.selfUserName = document.querySelector('#login-username').innerText;
  163.  
  164. let videoDanmuFilterObserver = new MutationObserver((recordList, observer) => {
  165. if(this.getOptions('filter_video_danmu', 1) != 1) return;
  166.  
  167. let hasNewfilterDanmu = false;
  168. recordList.forEach((record) => {
  169. if(record.type !== 'childList') return;
  170. for(let i = 0; i < record.addedNodes.length; i++)
  171. {
  172. let danmuElement = record.addedNodes[i];
  173. if(parseFloat(danmuElement.style.borderWidth) > 0 && this.getOptions('filter_skip_me', 1) == 1) continue;
  174.  
  175. let danmuText = danmuElement.innerText.trim();
  176. if(danmuText.length == 0)
  177. {
  178. if(this.getOptions('filter_icon_danmu', 1) != 1) continue;
  179. danmuText = '<表情>';
  180. }
  181. let filterDanmuText = danmuText;
  182.  
  183. let repeatMatches = danmuText.match(/^(.+?)\1+$/);
  184. if(repeatMatches) filterDanmuText = repeatMatches[1] + ' ...';
  185. if(!this.recordDanmuList[filterDanmuText]) this.recordDanmuList[filterDanmuText] = 0;
  186. let repeatCount = ++this.recordDanmuList[filterDanmuText];
  187. if(repeatCount == 1) continue;
  188.  
  189. danmuElement.remove();
  190.  
  191. // 统计新增
  192. this.filterCount++;
  193. hasNewfilterDanmu = true;
  194.  
  195. // 更新最后过滤弹幕文本
  196. document.querySelector('#J-room-chat-filter-last').innerHTML = '(' + repeatCount + ') ' + danmuText;
  197.  
  198. // 追加
  199. let alreadyExist = false;
  200. for(let i = 0; i < this.filterTopList.length; i++)
  201. {
  202. if(filterDanmuText === this.filterTopList[i].danmu)
  203. {
  204. this.filterTopList[i].count = repeatCount;
  205. alreadyExist = true;
  206. break;
  207. }
  208. }
  209. if(!alreadyExist) this.filterTopList.push({danmu: filterDanmuText, count: repeatCount});
  210. }
  211. });
  212.  
  213. if(hasNewfilterDanmu)
  214. {
  215. // 从多到少排序
  216. this.filterTopList.sort((a, b) => b.count - a.count);
  217.  
  218. // 删除多余
  219. while(this.filterTopList.length > 10) this.filterTopList.pop();
  220.  
  221. // 更新统计信息显示
  222. this.showFilterCountInfo();
  223. }
  224. });
  225.  
  226. videoDanmuFilterObserver.observe(videoDanmuDiv, {childList: true});
  227.  
  228. let chatDanmuFilterObserver = new MutationObserver((recordList, observer) => {
  229. if(this.getOptions('filter_chat_danmu', 1) != 1) return;
  230. recordList.forEach((record) => {
  231. if(record.type !== 'childList') return;
  232. for(let i = 0; i < record.addedNodes.length; i++)
  233. {
  234. let danmuElement = record.addedNodes[i];
  235. let danmuUserNameElement = danmuElement.querySelector('.name');
  236. // 不是用户发言
  237. if(!danmuUserNameElement) continue;
  238.  
  239. let danmuUserName = danmuUserNameElement.innerText;
  240. if(danmuUserName == this.selfUserName && this.getOptions('filter_skip_me', 1) == 1) continue;
  241.  
  242. let danmuText = danmuElement.querySelector('.msg').innerText.trim();
  243. if(danmuText.length == 0)
  244. {
  245. if(this.getOptions('filter_icon_danmu', 1) != 1) continue;
  246. danmuText = '<表情>';
  247. }
  248. let filterDanmuText = danmuText;
  249.  
  250. let repeatMatches = danmuText.match(/^(.+?)\1+$/);
  251. if(repeatMatches) filterDanmuText = repeatMatches[1] + ' ...';
  252.  
  253. if(!this.recordDanmuList[filterDanmuText]) continue;
  254. let repeatCount = this.recordDanmuList[filterDanmuText];
  255. if(repeatCount <= 1) continue;
  256.  
  257. danmuElement.remove();
  258. }
  259. });
  260. });
  261.  
  262. chatDanmuFilterObserver.observe(chatDanmuDiv, {childList: true});
  263. }
  264. };
  265.  
  266. HuyaFilter.start();
  267. HuyaFilter.createUI();
  268. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址