YouTube Chat Filter

Filters messages in YouTube stream chat.

当前为 2021-07-31 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Chat Filter
  3. // @namespace https://greasyfork.org/users/696211-ctl2
  4. // @version 0.1
  5. // @description Filters messages in YouTube stream chat.
  6. // @author Callum Latham
  7. // @match *://www.youtube.com/*
  8. // @match *://youtube.com/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (() => {
  13. if (window.frameElement.id !== 'chatframe') {
  14. return;
  15. }
  16.  
  17. const FILTER = [
  18. {
  19. 'streamer': /^/,
  20. 'author': /$./,
  21. 'message': /[abcdefghijklmnopqrstuvxyz]/i,
  22. 'requireBadge': true,
  23. 'limit': 1000,
  24. 'stopOnHover': true
  25. }
  26. ];
  27. const {clientHeight} = window.document.body;
  28. const spaces = Math.floor(clientHeight / 20);
  29.  
  30. (function style() {
  31. const addStyle = (sheet, selector, rules) => {
  32. const ruleString = rules.map(
  33. ([selector, rule]) => `${selector}:${typeof rule === 'function' ? rule() : rule};`
  34. );
  35.  
  36. sheet.insertRule(`${selector}{${ruleString.join('')}}`);
  37. };
  38.  
  39. const styleElement = document.createElement('style');
  40. const {sheet} = document.head.appendChild(styleElement);
  41.  
  42. const styles = [
  43. ['#item-offset', [
  44. ['height', `${clientHeight * 0.91}px`]
  45. ]],
  46. ['#items:not(.cf)', [
  47. ['display', 'none']
  48. ]],
  49. ['#items.cf > :nth-child(even)', [
  50. ['background-color', '#1f1f1f']
  51. ]]
  52. ];
  53.  
  54. for (const style of styles) {
  55. addStyle(sheet, style[0], style[1]);
  56. }
  57. })();
  58.  
  59. window.onload = async () => {
  60. const filter = (() => {
  61. const streamer = parent.document.querySelector('#meta').querySelector('#channel-name').innerText;
  62.  
  63. for (const {'streamer': regex, ...filter} of FILTER) {
  64. if (regex.test(streamer)) {
  65. return filter;
  66. }
  67. }
  68. })();
  69.  
  70. // Terminate if there's no filter to apply
  71. if (!filter) {
  72. return;
  73. }
  74.  
  75. const chatElements = {'held': document.body.querySelector('#chat').querySelector('#items')};
  76.  
  77. chatElements.accepted = chatElements.held.cloneNode(false);
  78.  
  79. chatElements.accepted.classList.add('cf');
  80. chatElements.held.parentElement.appendChild(chatElements.accepted);
  81.  
  82. let queuedPost;
  83. let doQueue = false;
  84. let hovered = false;
  85.  
  86. function acceptPost(post = queuedPost) {
  87. if (!post) {
  88. return;
  89. }
  90.  
  91. const doDelay = (doQueue || (filter.stopOnHover && hovered));
  92.  
  93. if (doDelay) {
  94. queuedPost = post;
  95. } else {
  96. const container = chatElements.accepted;
  97.  
  98. container.appendChild(post);
  99.  
  100. // Save memory by deleting passed posts
  101. while (container.children.length > spaces) {
  102. container.firstChild.remove();
  103. }
  104.  
  105. doQueue = true;
  106. queuedPost = undefined;
  107. }
  108. }
  109.  
  110. // Unqueue at regular intervals
  111. window.setInterval(() => {
  112. doQueue = false;
  113.  
  114. acceptPost();
  115. }, filter.limit);
  116.  
  117. window.document.body.addEventListener('mouseenter', () => {
  118. hovered = true;
  119. });
  120. window.document.body.addEventListener('mouseleave', () => {
  121. hovered = false;
  122.  
  123. acceptPost();
  124. });
  125.  
  126. function processPost(post) {
  127. chatElements.held.parentElement.style.removeProperty('height');
  128.  
  129. try {
  130. const isValid = !(
  131. filter.author.test(post.querySelector('#author-name').textContent) ||
  132. filter.message.test(post.querySelector('#message').textContent) ||
  133. (filter.requireBadge && !post.querySelector('#chat-badges').hasChildNodes())
  134. );
  135.  
  136. if (isValid) {
  137. acceptPost(post);
  138. } else {
  139. // Save memory by deleting rejected posts
  140. post.remove();
  141. }
  142. } catch (e) {
  143. console.group('STRANGE POST');
  144. console.warn(post);
  145. console.warn(e);
  146. console.groupEnd();
  147. }
  148. }
  149.  
  150. new MutationObserver((mutations) => {
  151. for (const {addedNodes} of mutations) {
  152. addedNodes.forEach(processPost);
  153. }
  154. }).observe(
  155. chatElements.held,
  156. {childList: true}
  157. );
  158. };
  159. })();