Hide Bot Comments

Removes comments made by bots on websites such as YouTube.

当前为 2022-06-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Hide Bot Comments
  3. // @namespace https://theusaf.org
  4. // @version 1.9.1
  5. // @description Removes comments made by bots on websites such as YouTube.
  6. // @author theusaf
  7. // @match https://www.youtube.com/**
  8. // @match https://www.facebook.com/plugins/comments.php*
  9. // @match https://www.facebook.com/plugins/feedback.php*
  10. // @copyright 2022 theusaf
  11. // @license MIT
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. const SITES = Object.freeze({
  16. YOUTUBE: {
  17. checks: [
  18. // starts with too much whitespace
  19. /^\s{2,}/,
  20. // only links and other punctuation
  21. /^(\s*@.+)?\s*(https:\/\/[^\s]+)(https:\/\/[^\s]+|\n.\s])+$/,
  22. // all caps and a link
  23. /^(\s*@.+)?\s*[A-Z\s\r\n!]*https:\/\/[^\s]+[A-Z\s\r\n!]*$/,
  24. // A link and a random message afterwards
  25. /^(\s*@.+)?\s*https:\/\/[^\s]+(\n|.|\s)*(It'll blow your mind\.|[dD]on'?t [mM]iss|Bots for u|Finally|💜|fax|only until|Bots are|:]|\.\.?\.$|I found it :|Do not miss this|:)|Ye[sp] ¤? (true|exactly)/i,
  26. // word + link
  27. /^(\s*@.+)?\s*(This|[Ww]ow!?)\s*https:\/\/[^\s]+/,
  28. // phrase + line + link
  29. /(is a brain burner.*|10,000.*?!|by having this:|it.?s finally here|Finally it's here\.?|deceives.*subscribers:\.{1,}|you .*will never love.*|[\u0401\u0451\u0410-\u044f,.:]{15,}.*|HOW STRONG IS KETTLE\?!|EXPOSED:|IS FREAK!|IS GARBAGE!{1,}|shocking truth.*|his subscribers.*|will stop watching.*|yes\.?|THE GAME.*|After watching this video you will never love.*)(\n|\s)(\n|.)*https:\/\/[^\s]+/,
  30. // link + random "word"
  31. /^(\s*@.+)?\s*https:\/\/[^\s]+\s*[a-z]+\s*$/,
  32. // link with a star at the end??
  33. /https:\/\/youtu.be\/\w+\*/,
  34. // ...
  35. /SWEET-GIRL|xvideos|specialdate|HOTGIRL|PRIVATE S\*X|over 18|Anna is a beautiful girl/i,
  36. // suspicious websites
  37. /beautyzone\.\w+|\.cam|lust\.\w+|\.host|\.uno|\.fun|asian\w*\.\w+|she.*\.online|\w*teen\.\w+/i,
  38. // too many "-"
  39. /-{5,}/,
  40. // single, somewhat strange word
  41. /^(Hii|Ye|Bruhh|Aawww?|🅱🆁🆄🅷)$/,
  42. // common phrase
  43. / (● ´ω ●) ✨💕|I POST [A-Z\s]*?VIDEOS|HATE COMMENT|I can read you mind brother|SPECIAL FOR YOU|l1ke my v1deo|small channel trying to grow| YouT\*ber|MY CONTENT|MY NAME|at my profile|My video|pedophile😱|MY WORLD RECORD|(^Yes.{0,5}$)|said this to a fan|Read my name|[Mm]y mom.*subscribers|r[\.\s]e[\.\s]a[\.\s]d[\.\s]? m[\.\s]y[\.\s]? n[\.\s]a[\.\s]m[\.\s]e|literally begging|MY VIDEOS?|my playlist|fucking cringe|[Dd][Oo][Nn].?[Tt] read my name/,
  44. // replies to bots/about bots
  45. /@Don'?t read my|^(ro)?bot+$|with bots|there are.*bots|oh god.*bots/i,
  46. // upside down chars
  47. /[ㄥϛㄣƐᄅƖ⅄Λ∩┴ɹԀ˥ʞſפℲƎƆ∀ʎʍʌʇɹɯʞɾᴉɥƃɟǝɔɐ]/,
  48. // just a single, weird character
  49. /^.$/,
  50. // invisible characters
  51. /[\u200e]/u,
  52. (text) => {
  53. const charSets = [
  54. {
  55. regex: /[\u{fe27}-\u{fe2f}\u{1df5}-\u{1dff}\u{1dc0}-\u{1de6}\u{1ab0}-\u{1abe}\u{0300}-\u{0333}\u{0339}-\u{033f}\u{0346}-\u{034a}\u{034b}-\u{034e}\u{0350}-\u{0357}\u{0358}-\u{035b}]/gu, // weird combining characters
  56. matchPercent: 0.4
  57. },
  58. {
  59. regex: /[ᴀʙᴄᴅᴇғɢʜɪᴊᴋʟᴍɴᴏᴘᴏ̨ʀsᴛᴜᴠᴡxʏᴢ\s]/g,
  60. matchPercent: 0.5
  61. },
  62. {
  63. regex: /[\u{1D538}-\u{1D56B}\u{1D400}-\u{1D433}]/gu, // math letter symbols
  64. matchPercent: 0.3
  65. }
  66. ];
  67. for (const check of charSets) {
  68. const { regex, matchPercent } = check,
  69. matches = text.match(regex)?.length ?? 0;
  70. if (matches / text.length > matchPercent && text.length > 10) {
  71. return true;
  72. }
  73. }
  74. }
  75. ]
  76. },
  77. FACEBOOK_EMBED: {
  78. checks: [
  79. // "Easy cash" scams
  80. /easy cash|work online|real passive income|(making|paid) over \$?\d+k?|salary from home/,
  81. // Scammy manga sites
  82. /(I liked it.*?recommend|you should try:|[Ss]hare a cartoon website|top [a-z]*?(comic|website)|there is no cost|try this one out|[Jj]ust read this|you [a-z\s]*?want [a-z\s]*?manga|(tons|a lot) of [a-z\s]*?man[gh][wu]?a|You can find the last part here|looking forward to seeing where this goes|YET ANOTHER RECOMMENDATION|enjoy another manga|I prefer this type of comic|hottest comics|Google led me|will love this one|I like this one: |FEE IS FREE|another [a-z\s]*?manga|WEBSITE[A-Z\s]*FREE|good read|must check this out|read more:|300 or more chapters|comics for free|website [a-z\s]*?manga:|favorite mange which I have read|\*{1,} SPOILER ALERT \*{1,}|FREE ACCESS|FREE (TO|FOR) READ).*(\n\s)*(https?:\/\/[^\s]+|\n.\s])+/,
  83. /geoagiphy\.com|.giphy\.com/,
  84. /(manga|story|site|website).*?:\s?(https?:\/\/[^\s]+|\n.\s])+$/,
  85. // Other weird comments/scams
  86. /look at a website|very popular .*?website|Amazon gift card/,
  87. /^i love sex$/
  88. ],
  89. options: {
  90. initialScan: () => {
  91. return document.querySelectorAll(".clearfix");
  92. }
  93. }
  94. }
  95. }),
  96. site = getCurrentSite(),
  97. commentMutationListener = new MutationObserver((mutations) => {
  98. for (const mutation of mutations) {
  99. for (const node of mutation.addedNodes) {
  100. const text = getCommentText(node, site);
  101. if (text) {
  102. if (isCommentLikelyBotComment(text, site)) {
  103. node.style.display = "none";
  104. }
  105. }
  106. }
  107. }
  108. });
  109.  
  110. commentMutationListener.observe(document.body, {
  111. subtree: true,
  112. childList: true
  113. });
  114.  
  115. /**
  116. * Determines whether a comment is likely spam.
  117. *
  118. * @param {String} text The comment's content
  119. * @param {Object} site The website the comment is from
  120. * @return {Boolean}
  121. */
  122. function isCommentLikelyBotComment(text, site) {
  123. for (const check of site.checks) {
  124. if (typeof check === "function") {
  125. if (check(text)) {
  126. console.log("Filter Check Failed");
  127. console.log(text);
  128. return true;
  129. }
  130. } else {
  131. // assume regex
  132. if (check.test(text)) {
  133. console.log("Regex Check Failed");
  134. console.log(check);
  135. console.log(text);
  136. return true;
  137. }
  138. }
  139. }
  140. return false;
  141. }
  142.  
  143. function getCommentText(node, site) {
  144. switch (site) {
  145. case SITES.YOUTUBE: {
  146. if (node.nodeName === "YTD-COMMENT-RENDERER") {
  147. return node.querySelector("#content-text").textContent;
  148. }
  149. break;
  150. }
  151. case SITES.FACEBOOK_EMBED: {
  152. if (node.classList?.contains("clearfix")) {
  153. try {
  154. return node?.lastElementChild
  155. .lastElementChild
  156. .lastElementChild
  157. .firstElementChild
  158. .children[1]
  159. .textContent;
  160. } catch (err) {
  161. return null;
  162. }
  163. }
  164. }
  165. }
  166. return null;
  167. }
  168.  
  169. function getCurrentSite() {
  170. switch (location.hostname) {
  171. case "www.youtube.com": {
  172. return SITES.YOUTUBE;
  173. }
  174. case "www.facebook.com": {
  175. return SITES.FACEBOOK_EMBED;
  176. }
  177. }
  178. }
  179.  
  180. if (site.options?.initialScan) {
  181. const items = site.options.initialScan();
  182. for (const node of items) {
  183. const text = getCommentText(node, site);
  184. if (text) {
  185. if (isCommentLikelyBotComment(text, site)) {
  186. node.style.display = "none";
  187. }
  188. }
  189. }
  190. }