Hide Bot Comments

Removes comments made by bots on websites such as YouTube.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Hide Bot Comments
// @namespace    https://theusaf.org
// @version      1.14.3
// @description  Removes comments made by bots on websites such as YouTube.
// @author       theusaf
// @match        https://www.youtube.com/**
// @match        https://www.facebook.com/plugins/comments.php*
// @match        https://www.facebook.com/plugins/feedback.php*
// @copyright    2022 theusaf
// @license      MIT
// @grant        none
// ==/UserScript==

const SITES = Object.freeze({
    YOUTUBE: {
      hostname: "www.youtube.com",
      checks: [
        // starts with too much whitespace
        /^\s{2,}/,
        // only links and other punctuation
        /^(\s*@.+)?\s*(https:\/\/[^\s]+)(https:\/\/[^\s]+|\n.\s])+$/,
        // all caps and a link
        /^(\s*@.+)?\s*[A-Z\s\r\n!]*https:\/\/[^\s]+[A-Z\s\r\n!]*$/,
        // A link and a random message afterwards
        /^(\s*@.+)?\s*https:\/\/[^\s]+(\n|.|\s)*(Here'?s the full video.*?this video|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,
        // word + link
        /^(\s*@.+)?\s*(This|[Ww]ow!?|Last fight|Yo)\s*https:\/\/[^\s]+/,
        // phrase + line + link
        /((Here is the final video|Here'?s the clip thank|Here is the (full|trending) clip|^Link to the clip|Finally.*?the clip u all|is a brain burner|Let'?s be honest we|LET'?S BE HONEST WE|I have been waiting so long|by having this:|[iI]t.?s finally here|Finally.*is finally here|it is finally there|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|THE GAME|After watching this video you will never love).*|(yes\.?|deceives.*subscribers:\.{1,}|Finally it'?s here\.?(\s*YES)?)|^Link to the clip part 2|10,000.*?!|This is the [Cc]lip u all.*:|Link to the clip\.? [Tt]hank me later|^Here you go|^yo\b|full vid -|\*YO\*🔥|GOLUM:)(\n|\s)(\n|.)*https:\/\/[^\s]+/,
        // various languages + line + link
        /^[\p{Script=Cyrillic}\s!\.]*(\n|\s)(\n|.)*https:\/\/((www|m)\.)?youtu[^\s]+/iu,
        // link + random "word"
        /^(\s*@.+)?\s*https:\/\/[^\s]+\s*[a-z]+\s*$/,
        // link with a star at the end??
        /https:\/\/youtu.be\/\w+\*/,
        // ...
        /SWEET-GIRL|xvideos|specialdate|HOTGIRL|PRIVATE S\*X|over 18|Anna is a beautiful girl|adult porn videos/i,
        // suspicious websites
        /beautyzone\.\w+|[a-z]+\.online|\.cam|lust\.\w+|[a-z]+\.monster|\.host|\.uno|\.fun|asian\w*\.\w+|she.*\.online|\w*teen\.\w+/i,
        // too many "-"
        /-{5,}/,
        // single, somewhat strange word
        /^([ĤHh]ii|Ye|[Bb]ruhh|[Aa]awww?|🆁🆄🅷\s?!*)$/,
        // common phrase
        /Send(.|\n)*?direct message(.|\n)*?(won a gift|your prize)|BECOME THE MOST HATED|Thanks for watching..? messages|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|I AM THE BEE?ST YOUU?TUBER|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/,
        // replies to bots/about bots
        /they are bots|get out.*bots|full of bots|bots be like|all these bots|the bots.*not|already bots|When the bots|@.*a bot|@Don'?t read|@.*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,
        // upside down chars
        /[ㄥϛㄣƐᄅƖ⅄Λ∩┴ɹԀ˥ʞſפℲƎƆ∀ʎʍʌʇɹɯʞɾᴉɥƃɟǝɔɐ]/,
        // just a single, weird character
        /^.$/s,
        // invisible characters
        /[\u200e]/u,
        (text) => {
          const matches = text.match(/[\u{0E80}-\u{0EFF}]/gu)?.length ?? 0;
          if (
            matches / text.length > 0.5 &&
            /Don.?t tran?slate|Do not tran?slate/i.test(text)
          ) {
            return true;
          }
        },
        (text) => {
          const charSets = [
            {
              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
              matchPercent: 0.4,
            },
            {
              regex: /[ᴀʙᴄᴅᴇғɢʜɪᴊᴋʟᴍɴᴏᴘᴏ̨ʀsᴛᴜᴠᴡxʏᴢ\s]/g,
              matchPercent: 0.5,
            },
            {
              regex: /[\u{1D538}-\u{1D56B}\u{1D400}-\u{1D433}]/gu, // math letter symbols
              matchPercent: 0.3,
            },
          ];
          for (const check of charSets) {
            const { regex, matchPercent } = check,
              matches = text.match(regex)?.length ?? 0;
            if (matches / text.length > matchPercent && text.length > 10) {
              return true;
            }
          }
        },
        // Username checks
        (_, node) => {
          const usernameNode = node.querySelector("#author-text"),
            userImage = node.querySelector("#img"),
            username = usernameNode.textContent.trim(),
            BAD_NAMES = [
              // remove
              /^SUB FOR SUB$/,
            ],
            HIDE_NAMES = [
              // don't remove, just hide pic and name
            ];
          for (const regex of BAD_NAMES) {
            if (regex.test(username)) {
              return true;
            }
          }
          for (const regex of HIDE_NAMES) {
            if (regex.test(username)) {
              userImage.src = "data:application/svg+xml,<svg></svg>";
            }
          }
          return false;
        },
      ],
      getCommentText(node) {
        if (node.nodeName === "YTD-COMMENT-RENDERER") {
          return node.querySelector("#content-text").textContent;
        }
      },
    },
    FACEBOOK_EMBED: {
      hostname: "www.facebook.com",
      checks: [
        // "Easy cash" scams
        /easy cash|earning money is very easy.*https?:\/\/|work online|real passive income|(making|paid|get) over \$?\d+k?|salary from home/s,
        // Scammy manga sites
        /(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])+/,
        /geoagiphy\.com|.giphy\.com/,
        /(manga|story|site|website).*?:\s?(https?:\/\/[^\s]+|\n.\s])+$/,
        // Other weird comments/scams
        /look at a website|very popular .*?website|Amazon gift card/,
        /^i love sex$/,
      ],
      options: {
        initialScan: () => {
          return document.querySelectorAll(".clearfix");
        },
      },
      getCommentText(node) {
        if (node.classList?.contains("clearfix")) {
          try {
            return node?.lastElementChild.lastElementChild.lastElementChild
              .firstElementChild.children[1].textContent;
          } catch (err) {
            return null;
          }
        }
      },
    },
  }),
  site = getCurrentSite(),
  commentMutationListener = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
      for (const node of mutation.addedNodes) {
        const text = site.getCommentText(node);
        if (text) {
          if (isCommentLikelyBotComment(text, site, node)) {
            node.style.display = "none";
          }
        }
      }
    }
  });

commentMutationListener.observe(document.body, {
  subtree: true,
  childList: true,
});

/**
 * Determines whether a comment is likely spam.
 *
 * @param {string} text The comment's content
 * @param {object} site The website the comment is from
 * @param {Node}   node
 * @return {Boolean}
 */
function isCommentLikelyBotComment(text, site, node) {
  for (const check of site.checks) {
    if (typeof check === "function") {
      if (check(text, node)) {
        console.log("Filter Check Failed");
        console.log(text);
        return true;
      }
    } else {
      // assume regex
      if (check.test(text)) {
        console.log("Regex Check Failed");
        console.log(check);
        console.log(text);
        return true;
      }
    }
  }
  return false;
}

function getCurrentSite() {
  for (let key in SITES) {
    const site = SITES[key];
    if (location.hostname === site.hostname) {
      return site;
    }
  }
}

if (site.options?.initialScan) {
  const items = site.options.initialScan();
  for (const node of items) {
    const text = site.getCommentText(node);
    if (text) {
      if (isCommentLikelyBotComment(text, site, node)) {
        node.style.display = "none";
      }
    }
  }
}