您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Blocks all emojis except flags from being displayed on any website (text, images, CSS) — Unicode-aware, reentrancy-guarded
// ==UserScript== // @name Nomoji (Unicode-aware, flags preserved) // @match *://*/* // @run-at document-start // @grant none // @version 0.4.0 // @namespace https://greasyfork.org/users/1497719 // @description Blocks all emojis except flags from being displayed on any website (text, images, CSS) — Unicode-aware, reentrancy-guarded // ==/UserScript== (() => { // Unicode property-based matching const EMOJI_CLUSTER = /\p{Extended_Pictographic}(?:\uFE0F)?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F)?)*?/u; const FLAG_SEQUENCE = /[\u{1F1E6}-\u{1F1FF}]{2}/u; const emojiTestRE = new RegExp( `(?:${FLAG_SEQUENCE.source})|(?:${EMOJI_CLUSTER.source})`, 'u' ); const emojiReplaceRE = new RegExp( `(${FLAG_SEQUENCE.source})|(${EMOJI_CLUSTER.source})`, 'gu' ); const SKIP_TAGS = new Set(['SCRIPT', 'STYLE', 'CODE', 'PRE', 'NOSCRIPT', 'TEXTAREA']); const EDITABLE_INPUTS = new Set(['INPUT', 'TEXTAREA']); const isEditable = (el) => el && el.nodeType === Node.ELEMENT_NODE && (EDITABLE_INPUTS.has(el.tagName) || el.isContentEditable); const isSkippable = (el) => el && el.nodeType === Node.ELEMENT_NODE && SKIP_TAGS.has(el.tagName); let processing = false; const stripTextNode = (node) => { if (!node || node.nodeType !== Node.TEXT_NODE) return; const parent = node.parentNode; if (!parent || isEditable(parent) || isSkippable(parent)) return; const val = node.nodeValue; if (!val || !emojiTestRE.test(val)) return; node.nodeValue = val .replace(emojiReplaceRE, (m, flag) => (flag ? flag : '')) .replace(/\uFE0F/gu, ''); // remove stray VS16 }; const processElement = (el) => { if (isEditable(el) || isSkippable(el)) return; for (const attr of ['aria-label', 'title', 'alt']) { if (el.hasAttribute && el.hasAttribute(attr)) { const v = el.getAttribute(attr); if (v && emojiTestRE.test(v)) { el.setAttribute( attr, v.replace(emojiReplaceRE, (m, flag) => (flag ? flag : '')).replace(/\uFE0F/gu, '') ); } } } const tag = el.tagName; if (tag === 'IMG' || tag === 'SVG' || tag === 'PICTURE') { const src = (el.getAttribute && el.getAttribute('src')) || ''; const role = (el.getAttribute && el.getAttribute('role')) || ''; const cls = (el.className && String(el.className)) || ''; const looksEmoji = /twemoji|emoji|emojione|noto-emoji|blobcat|blobmoji/i.test(src + ' ' + role + ' ' + cls); if (looksEmoji) { el.style.setProperty('visibility', 'hidden', 'important'); el.style.setProperty('width', '0px', 'important'); el.style.setProperty('height', '0px', 'important'); el.style.setProperty('overflow', 'hidden', 'important'); return; } } const styleAttr = el.getAttribute && el.getAttribute('style'); if (styleAttr && /background(-image)?:/i.test(styleAttr)) { let bg = ''; try { bg = getComputedStyle(el).backgroundImage || ''; } catch {} if (/emoji|twemoji/i.test(bg)) { el.style.setProperty('background-image', 'none', 'important'); } } }; const processSubtree = (root) => { if (!root) return; if (root.nodeType === Node.TEXT_NODE) { stripTextNode(root); return; } if (root.nodeType !== Node.ELEMENT_NODE) return; if (!isSkippable(root)) processElement(root); const walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, { acceptNode: (node) => { if (node.nodeType === Node.ELEMENT_NODE) { return isSkippable(node) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT; } if (node.nodeType === Node.TEXT_NODE) { const p = node.parentNode; if (!p || isEditable(p) || isSkippable(p)) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } return NodeFilter.FILTER_SKIP; } } ); let node = walker.currentNode; do { if (node.nodeType === Node.TEXT_NODE) { stripTextNode(node); } else if (node.nodeType === Node.ELEMENT_NODE) { processElement(node); } node = walker.nextNode(); } while (node); }; const init = () => { if (!document.body) { requestAnimationFrame(init); return; } processing = true; try { processSubtree(document.body); } finally { processing = false; } }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } let queue = new Set(); let scheduled = false; const flush = () => { scheduled = false; if (queue.size === 0) return; processing = true; observer.disconnect(); try { for (const node of queue) processSubtree(node); } finally { queue.clear(); processing = false; observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); } }; const schedule = (node) => { if (!node) return; if (node.nodeType === Node.TEXT_NODE || node.nodeType === Node.ELEMENT_NODE) { queue.add(node); } if (!scheduled) { scheduled = true; Promise.resolve().then(flush); } }; const observer = new MutationObserver((mutations) => { if (processing) return; for (const m of mutations) { if (m.type === 'characterData') { schedule(m.target); } else if (m.type === 'childList') { for (const node of m.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) { schedule(node); } } } } }); observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); })();