// ==UserScript==
// @name noemoji
// @namespace http://tampermonkey.net/
// @version 2.0
// @description Blocks emojis on all websites (with special handling for X.com and YouTube)
// @author You
// @match *://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
// Unicode ranges for emojis
const emojiRanges = [
{ start: 0x1F600, end: 0x1F64F }, // Emoticons
{ start: 0x1F300, end: 0x1F5FF }, // Misc Symbols and Pictographs
{ start: 0x1F680, end: 0x1F6FF }, // Transport and Map
{ start: 0x1F700, end: 0x1F77F }, // Alchemical Symbols
{ start: 0x1F780, end: 0x1F7FF }, // Geometric Shapes
{ start: 0x1F800, end: 0x1F8FF }, // Supplemental Arrows-C
{ start: 0x1F900, end: 0x1F9FF }, // Supplemental Symbols and Pictographs
{ start: 0x1FA00, end: 0x1FA6F }, // Chess Symbols
{ start: 0x1FA70, end: 0x1FAFF }, // Symbols and Pictographs Extended-A
{ start: 0x2600, end: 0x26FF }, // Misc symbols
{ start: 0x2700, end: 0x27BF }, // Dingbats
{ start: 0x2300, end: 0x23FF }, // Miscellaneous Technical
{ start: 0x2B00, end: 0x2BFF }, // Miscellaneous Symbols and Arrows
{ start: 0x3000, end: 0x303F } // CJK Symbols and Punctuation (includes some emoji-like symbols)
];
// Check if we're on specific sites that need special handling
const isYouTube = () => window.location.hostname.includes('youtube');
const isTwitter = () => window.location.hostname.includes('twitter') || window.location.hostname.includes('x.com');
const isFacebook = () => window.location.hostname.includes('facebook') || window.location.hostname.includes('fb.com');
const isInstagram = () => window.location.hostname.includes('instagram');
const isReddit = () => window.location.hostname.includes('reddit');
const isDiscord = () => window.location.hostname.includes('discord');
const isWhatsApp = () => window.location.hostname.includes('whatsapp');
const isTelegram = () => window.location.hostname.includes('telegram');
// Check if a character is an emoji
function isEmoji(char) {
const code = char.codePointAt(0);
if (!code) return false;
return emojiRanges.some(range => code >= range.start && code <= range.end);
}
// Check if text contains any emoji
function containsEmoji(text) {
if (!text) return false;
for (let i = 0; i < text.length; i++) {
const charCode = text.codePointAt(i);
if (charCode !== text.charCodeAt(i)) {
// This is a surrogate pair
const char = String.fromCodePoint(charCode);
if (isEmoji(char)) {
return true;
}
i++; // Skip the second part of the surrogate pair
} else {
const char = text.charAt(i);
if (isEmoji(char)) {
return true;
}
}
}
return false;
}
// Replace emojis with empty string
function removeEmojis(text) {
if (!text) return text;
let result = '';
for (let i = 0; i < text.length; i++) {
// Handle surrogate pairs correctly for emojis
const charCode = text.codePointAt(i);
if (charCode !== text.charCodeAt(i)) {
// This is a surrogate pair (likely an emoji)
const char = String.fromCodePoint(charCode);
if (isEmoji(char)) {
result += ''; // Remove emoji completely
i++; // Skip the second part of the surrogate pair
} else {
result += char;
i++; // Skip the second part of the surrogate pair
}
} else {
const char = text.charAt(i);
if (isEmoji(char)) {
result += ''; // Remove emoji completely
} else {
result += char;
}
}
}
return result;
}
// Process text nodes and elements
function processNode(node) {
if (node.nodeType === Node.TEXT_NODE) {
const originalText = node.nodeValue;
const newText = removeEmojis(originalText);
if (originalText !== newText) {
node.nodeValue = newText;
}
} else if (node.nodeType === Node.ELEMENT_NODE) {
// Skip script, style, and svg elements
if (['SCRIPT', 'STYLE', 'SVG'].includes(node.tagName)) {
return;
}
// Check for img elements that might be emoji
if (node.tagName === 'IMG') {
const alt = node.getAttribute('alt') || '';
const src = node.getAttribute('src') || '';
const className = node.className || '';
// Hide image if it's likely an emoji
if (containsEmoji(alt) ||
src.includes('emoji') ||
src.includes('twemoji') ||
className.includes('emoji') ||
(node.width > 0 && node.height > 0 && node.width < 32 && node.height < 32)) {
node.style.display = 'none';
}
}
// Process elements that might contain emoji
if (['SPAN', 'DIV', 'P', 'A', 'BUTTON', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(node.tagName)) {
const aria = node.getAttribute('aria-label') || '';
const title = node.getAttribute('title') || '';
const className = node.className || '';
if ((aria && containsEmoji(aria)) ||
(title && containsEmoji(title)) ||
className.includes('emoji')) {
// For X.com and other platforms that use spans to wrap emojis
if (node.childNodes.length === 0 ||
(node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE && containsEmoji(node.childNodes[0].nodeValue))) {
node.style.display = 'none';
}
}
}
// Process children recursively
Array.from(node.childNodes).forEach(processNode);
}
}
// Process elements specific to certain platforms
function processPlatformSpecificElements() {
// YouTube specific processing
if (isYouTube()) {
const youtubeSelectors = [
'yt-live-chat-text-message-renderer img',
'yt-formatted-string img',
'ytd-comment-renderer #content-text img',
'img[src*="emoji"]',
'img.emoji',
'yt-emoji',
'yt-emoji-picker-renderer'
];
youtubeSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.style.display = 'none';
});
});
}
// Twitter/X.com specific processing
if (isTwitter()) {
const twitterSelectors = [
'span[aria-label*="Emoji"]',
'img.Emoji',
'img[alt*="Emoji"]',
'.r-4qtqp9.r-1bwzh9t.r-dnmrzs.r-bnwqim',
'[data-testid="tweetText"] span:not(.r-18u37iz):not(:has(*))'
];
twitterSelectors.forEach(selector => {
try {
document.querySelectorAll(selector).forEach(el => {
el.style.display = 'none';
});
} catch (e) {
// Some complex CSS selectors might not be supported in all browsers
}
});
}
// Facebook specific processing
if (isFacebook()) {
const fbSelectors = [
'span[aria-label*="emoji"]',
'img[alt*="emoji"]',
'img[src*="emoji"]',
'.x1lliihq', // Facebook's emoji class
'img[alt*="Emoticon"]'
];
fbSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.style.display = 'none';
});
});
}
// Instagram specific processing
if (isInstagram()) {
const igSelectors = [
'span[aria-label*="emoji"]',
'img[alt*="emoji"]',
'img[src*="emoji"]',
'img._7yuu' // Instagram's emoji class
];
igSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.style.display = 'none';
});
});
}
// Reddit specific processing
if (isReddit()) {
const redditSelectors = [
'span[aria-label*="emoji"]',
'img.emoji',
'img[alt*="emoji"]',
'img[src*="emoji"]'
];
redditSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.style.display = 'none';
});
});
}
// Discord specific processing
if (isDiscord()) {
const discordSelectors = [
'img.emoji',
'img[alt*="emoji"]',
'img[src*="emoji"]',
'span.emoji'
];
discordSelectors.forEach(selector => {
document.querySelectorAll(selector).forEach(el => {
el.style.display = 'none';
});
});
}
}
// Add custom CSS for universal emoji blocking
function addCustomStyle() {
const style = document.createElement('style');
style.textContent = `
/* Universal emoji hiding */
img.emoji,
img[src*="emoji"],
img[alt*="emoji"],
span[aria-label*="emoji"],
span.emoji,
.emoji,
[role="img"][aria-label*="emoji"],
[role="img"][aria-label*="Emoji"],
[role="img"][title*="emoji"],
[role="img"][title*="Emoji"] {
display: none !important;
}
/* Site-specific emoji hiding */
/* YouTube */
yt-emoji-picker-renderer,
yt-emoji,
yt-img-shadow:has(img[src*="emoji"]),
img[src*="emojiasset"],
yt-live-chat-paid-message-renderer img,
yt-live-chat-paid-sticker-renderer img,
ytd-comment-renderer #content-text img {
display: none !important;
}
/* X.com / Twitter */
.r-4qtqp9.r-1bwzh9t.r-dnmrzs.r-bnwqim,
span[aria-label*="Emoji"],
span[aria-label*="emoji"] {
display: none !important;
}
/* Facebook */
.x1lliihq,
img[alt*="Emoticon"] {
display: none !important;
}
/* WhatsApp Web */
span.selectable-text img,
div.selectable-text img {
display: none !important;
}
/* Slack */
.c-emoji,
.emoji {
display: none !important;
}
/* Gmail */
img[src*="emojione"],
img[src*="goomoji"] {
display: none !important;
}
/* Discord */
img.emoji-4QhjQqh,
span.emojiContainer-3X8SvE {
display: none !important;
}
/* Common classes across platforms */
.emoji-mart-emoji,
.emoji-picker,
.emoji-container,
.emoji-wrapper {
display: none !important;
}
`;
document.head.appendChild(style);
}
// Main initialization function
function init() {
addCustomStyle();
// Process the entire document
processNode(document.body);
// Process platform-specific elements
processPlatformSpecificElements();
// Set up a MutationObserver to handle dynamically added content
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
processNode(node);
});
});
// Re-process platform specific elements after DOM changes
processPlatformSpecificElements();
});
// Observe the entire document for changes
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
}
// Initialize as soon as possible
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
// Handle SPA navigation
let lastUrl = location.href;
new MutationObserver(() => {
const url = location.href;
if (url !== lastUrl) {
lastUrl = url;
// Wait a bit for content to load after navigation
setTimeout(() => {
processNode(document.body);
processPlatformSpecificElements();
}, 1000);
}
}).observe(document, {subtree: true, childList: true});
// Apply initial styling even before full DOM load
// This helps catch emoji that load very early
(function earlyInit() {
// Add basic style to prevent flash of emoji
const earlyStyle = document.createElement('style');
earlyStyle.textContent = `
img[src*="emoji"], img.emoji, .emoji {
display: none !important;
}
`;
// Try to insert style as early as possible
if (document.head) {
document.head.appendChild(earlyStyle);
} else {
// If head isn't available yet, wait for it
const observer = new MutationObserver(() => {
if (document.head) {
document.head.appendChild(earlyStyle);
observer.disconnect();
}
});
observer.observe(document.documentElement, { childList: true, subtree: true });
}
})();
})();