Automatically hide YouTube comments based on username and spam text patterns.
// ==UserScript==
// @name YT Comment Filter (Improved)
// @supportURL https://gist.github.com/adimuhamad/143a06052413aaecb6ddf1a4e39103c1
// @namespace https://gist.github.com/adimuhamad/143a06052413aaecb6ddf1a4e39103c1
// @homepageURL https://gist.github.com/adimuhamad/143a06052413aaecb6ddf1a4e39103c1
// @version 4.0
// @description Automatically hide YouTube comments based on username and spam text patterns.
// @author Mochamad Adi MR (adimuham.mad)
// @match *://www.youtube.com/watch?v=*
// @match *://www.youtube.com/shorts/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @license MIT
// @compatible chrome
// @compatible firefox
// @compatible edge
// @compatible opera
// @compatible safari
// @run-at document-end
// ==/UserScript==
(function () {
"use strict";
let filterToggleButton = null;
let isShowingHidden = false;
const STORAGE_KEY = "yt_filter_custom_words";
const builtInForbiddenUsernames = ["vip"];
function loadCustomWords() {
const savedWords = GM_getValue(STORAGE_KEY, "");
if (savedWords) {
return savedWords.split(',').map(word => word.trim()).filter(Boolean);
}
return [];
}
function showSettingsPrompt() {
const currentWords = GM_getValue(STORAGE_KEY, "");
const newWords = prompt("Enter custom username (separate with commas)):\nno need to enter a username where the vowels\nare replaced with numbers", currentWords);
if (newWords === null) return;
GM_setValue(STORAGE_KEY, newWords);
alert("Custom usernames has saved!\nRefresh the page to apply the changes and see the results.");
}
function registerMenu() {
GM_registerMenuCommand("List Blocked Username", showSettingsPrompt);
}
const customForbiddenUsernames = loadCustomWords();
const forbiddenUsernames = Array.from(new Set([...builtInForbiddenUsernames, ...customForbiddenUsernames]));
const forbiddenRegex = /[^\u0000-\u007F\u00A0-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u02B0-\u02FF\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0800-\u083F\u0840-\u085F\u0860-\u087F\u08A0-\u08FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u125F\u1280-\u12BF\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1A20-\u1A5F\u1A80-\u1AFF\u1B00-\u1B7F\u1B80-\u1BBF\u1BC0-\u1BFF\u1C00-\u1C4F\u1C50-\u1C7F\u1C90-\u1CBF\u1CC0-\u1CCF\u1CD0-\u1CFF\u1E00-\u1EFF\u1F00-\u1FFF\u2000-\u206F\u2070-\u20CF\u20D0-\u20FF\u2150-\u218F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2DE0-\u2DFF\u2E00-\u2E7F\u2E80-\u2EFF\u2F00-\u2FDF\u2FF0-\u2FFF\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31A0-\u31BF\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA4D0-\uA4FF\uA500-\uA63F\uA640-\uA69F\uA6A0-\uA6FF\uA700-\uA71F\uA720-\uA7FF\uA800-\uA82F\uA830-\uA83F\uA840-\uA87F\uA880-\uA8DF\uA8E0-\uA8FF\uA900-\uA92F\uA930-\uA95F\uA960-\uA97F\uA980-\uA9DF\uA9E0-\uA9FF\uAA00-\uAA3F\uAA40-\uAA6F\uAA70-\uAAAB\uAAAC-\uAAAF\uAAB0-\uAABF\uAAC0-\uAADF\uAAE0-\uAAEF\uAAF0-\uAAFF\uAB00-\uAB2F\uAB30-\uAB6F\uAB70-\uABBF\uABC0-\uABFF\uAC00-\uD7AF\uD7B0-\uD7FF\uF900-\uFAFF\uFB00-\uFB4F\uFB50-\uFDFF\uFE00-\uFE0F\uFE10-\uFE1F\uFE20-\uFE2F\uFE30-\uFE4F\uFE50-\uFE6F\uFE70-\uFEFF]/;
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function createLeetPattern(word) {
let escapedWord = escapeRegExp(word);
return escapedWord.replace(/a/gi, "[a4]").replace(/i/gi, "[i1]").replace(/e/gi, "[e3]").replace(/o/gi, "[o0]").replace(/s/gi, "[s5]").replace(/t/gi, "[t7]").replace(/g/gi, "[g9]");
}
const forbiddenPatterns = forbiddenUsernames.map(createLeetPattern);
const forbiddenUserRegex = new RegExp(forbiddenPatterns.join("|"), "i");
function addGlobalStyles() {
GM_addStyle(`
.yt-comment-filter-hidden { display: none; }
body.yt-filter-showing-hidden .yt-comment-filter-hidden {
display: block !important;
opacity: 0.6;
border: 1px dashed rgba(255, 0, 0, 0.5);
border-radius: 8px;
margin-bottom: 8px !important;
}
#yt-filter-toggle-container {
display: inline-flex;
align-items: center;
vertical-align: middle;
cursor: pointer;
font-family: "Roboto","Arial",sans-serif;
font-size: 14px;
font-weight: 500;
color: var(--yt-spec-text-secondary);
margin-left: 32px;
position: relative;
bottom: 3px;
}
#yt-filter-toggle-container > div {
display: inline-flex;
align-items: center;
}
#yt-filter-toggle-container svg {
width: 24px;
height: 24px;
fill: var(--yt-spec-text-secondary);
margin-right: 4px;
}
#yt-filter-toggle-container .yt-filter-label,
#yt-filter-toggle-container .yt-filter-status {
text-transform: uppercase;
margin-right: 4px;
}
#yt-filter-toggle-container .icon-visibility-off { display: none; }
#yt-filter-toggle-container .icon-visibility-on { display: inline-block; }
body.yt-filter-showing-hidden #yt-filter-toggle-container .icon-visibility-off { display: inline-block; }
body.yt-filter-showing-hidden #yt-filter-toggle-container .icon-visibility-on { display: none; }
`);
}
function updateHiddenCount() {
if (!filterToggleButton) return;
const count = document.querySelectorAll(".yt-comment-filter-hidden").length;
const label = filterToggleButton.querySelector(".yt-filter-label");
const status = filterToggleButton.querySelector(".yt-filter-status");
const info = filterToggleButton.querySelector(".yt-filter-info");
if (!label || !status || !info) return;
label.textContent = "Hidden";
if (isShowingHidden) {
status.textContent = "OFF";
info.style.display = "none";
} else {
status.textContent = "ON";
info.style.display = "";
info.textContent = `(${count})`;
}
}
function injectFilterButton() {
if (document.getElementById("yt-filter-toggle-container")) return;
const targetElement = document.querySelector("ytd-comments-header-renderer #additional-section");
if (!targetElement) return;
filterToggleButton = document.createElement("span");
filterToggleButton.id = "yt-filter-toggle-container";
filterToggleButton.className = "style-scope ytd-comments-header-renderer";
const visibilityIconSVG = `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" focusable="false"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5C21.27 7.61 17 4.5 12 4.5zm0 13c-3.04 0-5.5-2.46-5.5-5.5S8.96 6.5 12 6.5s5.5 2.46 5.5 5.5-2.46 5.5-5.5 5.5zm0-9c-1.93 0-3.5 1.57-3.5 3.5s1.57 3.5 3.5 3.5 3.5-1.57 3.5-3.5-1.57-3.5-3.5-3.5z"></path></svg>`;
const visibilityOffIconSVG = `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" focusable="false"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L21.73 23 23 21.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.93 1.57 3.5 3.5 3.5.22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-3.04 0-5.5-2.46-5.5-5.5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.93-1.57-3.5-3.5-3.5l-.16.02z"></path></svg>`;
filterToggleButton.innerHTML = `<div><svg class="icon-visibility-on" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" focusable="false">${visibilityIconSVG}</svg><svg class="icon-visibility-off" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" focusable="false">${visibilityOffIconSVG}</svg><span class="yt-filter-label">Hidden</span><span class="yt-filter-status">OFF</span><span class="yt-filter-info">(0)</span></div>`;
filterToggleButton.addEventListener("click", () => {
isShowingHidden = !isShowingHidden;
document.body.classList.toggle("yt-filter-showing-hidden", isShowingHidden);
updateHiddenCount();
});
targetElement.after(filterToggleButton);
return true;
}
let throttleCooldown = false;
const throttleDelay = 1000;
function removeBadComments() {
const commentContainers = document.querySelectorAll("ytd-comment-view-model:not(.yt-comment-filter-hidden)");
commentContainers.forEach((commentContainer) => {
const commentTextElement = commentContainer.querySelector("#content-text");
const usernameElement = commentContainer.querySelector("#author-text span");
if (!commentTextElement) return;
let isSpam = false;
if (forbiddenRegex.test(commentTextElement.innerText)) isSpam = true;
if (!isSpam && usernameElement && forbiddenUserRegex.test(usernameElement.innerText)) isSpam = true;
if (isSpam) {
commentContainer.style.display = "none";
commentContainer.classList.add("yt-comment-filter-hidden");
if (commentContainer.id === "comment") {
const thread = commentContainer.closest("ytd-comment-thread-renderer");
if (thread) {
const repliesSection = thread.querySelector("div#replies");
if (repliesSection) {
repliesSection.style.display = "none";
}
}
}
}
});
updateHiddenCount();
}
function throttledRemoveBadComments() {
if (throttleCooldown) return;
removeBadComments();
throttleCooldown = true;
setTimeout(() => {
throttleCooldown = false;
}, throttleDelay);
}
registerMenu();
addGlobalStyles();
setTimeout(removeBadComments, 3000);
const observer = new MutationObserver(() => {
throttledRemoveBadComments();
});
observer.observe(document.body, { childList: true, subtree: true });
const buttonInjectInterval = setInterval(() => {
if (injectFilterButton()) {
clearInterval(buttonInjectInterval);
updateHiddenCount();
}
}, 1000);
})();