Hide Bot Comments

Removes comments made by bots on websites such as YouTube.

当前为 2022-08-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Hide Bot Comments
  3. // @namespace https://theusaf.org
  4. // @version 1.12.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. hostname: "www.youtube.com",
  18. checks: [
  19. // starts with too much whitespace
  20. /^\s{2,}/,
  21. // only links and other punctuation
  22. /^(\s*@.+)?\s*(https:\/\/[^\s]+)(https:\/\/[^\s]+|\n.\s])+$/,
  23. // all caps and a link
  24. /^(\s*@.+)?\s*[A-Z\s\r\n!]*https:\/\/[^\s]+[A-Z\s\r\n!]*$/,
  25. // A link and a random message afterwards
  26. /^(\s*@.+)?\s*https:\/\/[^\s]+(\n|.|\s)*(It'll blow your mind\.|This is where (the )?world'?s first " ?Rick Rolled" started\.?|[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,
  27. // word + link
  28. /^(\s*@.+)?\s*(This|[Ww]ow!?|Last fight|Yo)\s*https:\/\/[^\s]+/,
  29. // phrase + line + link
  30. /(Link to the clip\.? [Tt]hank me later|is a brain burner.*|^Link to the clip part 2|10,000.*?!|by having this:|it.?s finally here|Finally it's here\.?(\s*YES)?|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]+/,
  31. // various languages + line + link
  32. /^[\p{Script=Cyrillic}\s!\.]*(\n|\s)(\n|.)*https:\/\/((www|m)\.)?youtu[^\s]+/iu,
  33. // link + random "word"
  34. /^(\s*@.+)?\s*https:\/\/[^\s]+\s*[a-z]+\s*$/,
  35. // link with a star at the end??
  36. /https:\/\/youtu.be\/\w+\*/,
  37. // ...
  38. /SWEET-GIRL|xvideos|specialdate|HOTGIRL|PRIVATE S\*X|over 18|Anna is a beautiful girl|adult porn videos/i,
  39. // suspicious websites
  40. /beautyzone\.\w+|[a-z]+\.online|\.cam|lust\.\w+|[a-z]+\.monster|\.host|\.uno|\.fun|asian\w*\.\w+|she.*\.online|\w*teen\.\w+/i,
  41. // too many "-"
  42. /-{5,}/,
  43. // single, somewhat strange word
  44. /^(Hii|Ye|Bruhh|Aawww?|🆁🆄🅷\s?!*)$/,
  45. // common phrase
  46. /I'm not scared of ghosts,? and you\?|SCREAMING IN H[E3]LL BECAUSE MY.*?BETTER|I MADE.*VIDS|is bad i make better content|оп му с[hН]аппе[ІlL]|I MAKE.*CONTENT|my videos are better|^I.m better than|I UPLOAD.*VIDEO|I (make|made).*(video|content)| (● ´ω ●) ✨💕|[Oo]mg.*it.?s finally here|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/,
  47. // replies to bots/about bots
  48. /When the bots|@.*a bot|@Don'?t read my|@.*ok.*[Ii].*wont|remove bots|^(ro)?bot+$|with bots|hi bot|bots.*get worse|why are.*bots|bots.*everywhere|bot repl.*row|there are.{0,15}bots|oh god.*bots|report.*bots|so many.*?bots|holy bots|do nothing about bots|bots.*common/i,
  49. // upside down chars
  50. /[ㄥϛㄣƐᄅƖ⅄Λ∩┴ɹԀ˥ʞſפℲƎƆ∀ʎʍʌʇɹɯʞɾᴉɥƃɟǝɔɐ]/,
  51. // just a single, weird character
  52. /^.$/s,
  53. // invisible characters
  54. /[\u200e]/u,
  55. (text) => {
  56. const matches = text.match(/[\u{0E80}-\u{0EFF}]/gu)?.length ?? 0;
  57. if (matches / text.length > 0.5 && /Don.?t tran?slate|Do not tran?slate/i.test(text)) {
  58. return true;
  59. }
  60. },
  61. (text) => {
  62. const charSets = [
  63. {
  64. 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
  65. matchPercent: 0.4
  66. },
  67. {
  68. regex: /[ᴀʙᴄᴅᴇғɢʜɪᴊᴋʟᴍɴᴏᴘᴏ̨ʀsᴛᴜᴠᴡxʏᴢ\s]/g,
  69. matchPercent: 0.5
  70. },
  71. {
  72. regex: /[\u{1D538}-\u{1D56B}\u{1D400}-\u{1D433}]/gu, // math letter symbols
  73. matchPercent: 0.3
  74. }
  75. ];
  76. for (const check of charSets) {
  77. const { regex, matchPercent } = check,
  78. matches = text.match(regex)?.length ?? 0;
  79. if (matches / text.length > matchPercent && text.length > 10) {
  80. return true;
  81. }
  82. }
  83. }
  84. ],
  85. getCommentText(node) {
  86. if (node.nodeName === "YTD-COMMENT-RENDERER") {
  87. return node.querySelector("#content-text").textContent;
  88. }
  89. }
  90. },
  91. FACEBOOK_EMBED: {
  92. hostname: "www.facebook.com",
  93. checks: [
  94. // "Easy cash" scams
  95. /easy cash|earning money is very easy.*https?:\/\/|work online|real passive income|(making|paid|get) over \$?\d+k?|salary from home/s,
  96. // Scammy manga sites
  97. /(I liked it.*?recommend|try this manga.*?https?s:\/\/|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])+/,
  98. /geoagiphy\.com|.giphy\.com/,
  99. /(manga|story|site|website).*?:\s?(https?:\/\/[^\s]+|\n.\s])+$/,
  100. // Other weird comments/scams
  101. /look at a website|very popular .*?website|Amazon gift card/,
  102. /^i love sex$/
  103. ],
  104. options: {
  105. initialScan: () => {
  106. return document.querySelectorAll(".clearfix");
  107. }
  108. },
  109. getCommentText(node) {
  110. if (node.classList?.contains("clearfix")) {
  111. try {
  112. return node?.lastElementChild
  113. .lastElementChild
  114. .lastElementChild
  115. .firstElementChild
  116. .children[1]
  117. .textContent;
  118. } catch (err) {
  119. return null;
  120. }
  121. }
  122. }
  123. }
  124. }),
  125. site = getCurrentSite(),
  126. commentMutationListener = new MutationObserver((mutations) => {
  127. for (const mutation of mutations) {
  128. for (const node of mutation.addedNodes) {
  129. const text = site.getCommentText(node);
  130. if (text) {
  131. if (isCommentLikelyBotComment(text, site)) {
  132. node.style.display = "none";
  133. }
  134. }
  135. }
  136. }
  137. });
  138.  
  139. commentMutationListener.observe(document.body, {
  140. subtree: true,
  141. childList: true
  142. });
  143.  
  144. /**
  145. * Determines whether a comment is likely spam.
  146. *
  147. * @param {String} text The comment's content
  148. * @param {Object} site The website the comment is from
  149. * @return {Boolean}
  150. */
  151. function isCommentLikelyBotComment(text, site) {
  152. for (const check of site.checks) {
  153. if (typeof check === "function") {
  154. if (check(text)) {
  155. console.log("Filter Check Failed");
  156. console.log(text);
  157. return true;
  158. }
  159. } else {
  160. // assume regex
  161. if (check.test(text)) {
  162. console.log("Regex Check Failed");
  163. console.log(check);
  164. console.log(text);
  165. return true;
  166. }
  167. }
  168. }
  169. return false;
  170. }
  171.  
  172. function getCurrentSite() {
  173. for (let key in SITES) {
  174. const site = SITES[key];
  175. if (location.hostname === site.hostname) {
  176. return site;
  177. }
  178. }
  179. }
  180.  
  181. if (site.options?.initialScan) {
  182. const items = site.options.initialScan();
  183. for (const node of items) {
  184. const text = site.getCommentText(node);
  185. if (text) {
  186. if (isCommentLikelyBotComment(text, site)) {
  187. node.style.display = "none";
  188. }
  189. }
  190. }
  191. }