您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Blur WhatsApp Web content (names, DPs, media, emojis) with last 3 messages unblurred. Robust on scroll/load. Hover unblurs. Stable & maintainable.
// ==UserScript== // @name WhatsApp Web: Unblur with Scroll // @namespace wwpb_robust_cleaned_minimal // @version 1.0 // @description Blur WhatsApp Web content (names, DPs, media, emojis) with last 3 messages unblurred. Robust on scroll/load. Hover unblurs. Stable & maintainable. // @author https://greasyfork.org/en/users/1356925-govind-rajulu & Perplexity AI // @match https://web.whatsapp.com/ // @license MIT // @grant none // ==/UserScript== (function () { 'use strict'; const UNCENSOR_LAST_N = 3; const CLS = { MSG_BLUR: 'wwpb-blur-msg', MEDIA_BLUR: 'wwpb-blur-media', DP_BLUR: 'wwpb-blur-dp', NAME_BLUR: 'wwpb-blur-name' }; const SELECTORS = { SIDEBAR_AVATARS: [ 'img._ao3e', 'img[class*="_ao3e"]', 'img[class*="x1n2onr6"]', 'img[class*="xh8yej3"]', 'img._2WP9Q', 'img[src*="blob"]', 'img[decoding="async"]', 'div.x10l6tqk.x1agz8ms.xiy17q3.x18d0r48.xso031l.x1q0q8m5.x1jpdw5n.x5yr21d.x10l6tqk.xh8yej3[style*="background-image"]' ].join(','), SIDEBAR_NAMES: [ '#pane-side ._21S-L span[dir]', '#pane-side span[title]', '#pane-side ._21S-L span' ].join(','), CHAT_MESSAGES: 'div.message-in, div.message-out', CHAT_MSG_TEXT: 'span.selectable-text', CHAT_SPECIAL_SPANS: [ 'span._ahxu', 'span[class*="_ahxu"]', 'span._ao3e', 'span[class*="_ao3e"]', 'span[class*="x1iyjqo2"]' ].join(','), CHAT_EMOJIS: [ 'img[data-emoji]', 'img.emoji', 'img[alt*="emoji"]', 'img[src*="emoji"]', 'span._ahiu', 'span[class*="_ahiu"]' ].join(','), CHAT_MEDIA: 'img, video', CHAT_HEADER_NAMES: [ '#main header span[title]', '#main header div[role="heading"] span', '#main header span[dir="auto"][title]' ].join(','), CHAT_HEADER_SPECIAL_SPANS: [ 'span._ao3e', 'span[class*="_ao3e"]', 'span._ahxu', 'span[class*="_ahxu"]', 'span[class*="x1iyjqo2"]' ].join(','), CHAT_HEADER_AVATAR: '#main header img', CHAT_HEADER_DIV_AVATAR: '#main header div[style*="background-image"]', CHAT_SIDE_NAMES: 'span._21S-L, span._3FuDI, span[title]' }; const STYLE_ID = 'wwpb-blur-style-robust-cleaned-minimal'; if (!document.getElementById(STYLE_ID)) { const style = document.createElement('style'); style.id = STYLE_ID; style.textContent = ` .${CLS.MSG_BLUR}, .${CLS.MEDIA_BLUR}, .${CLS.DP_BLUR} { filter: blur(5px); transition: filter 0.16s; cursor: pointer; display: inline-block; } .${CLS.DP_BLUR} { filter: blur(10px); } .${CLS.MSG_BLUR}:hover, .${CLS.MEDIA_BLUR}:hover, .${CLS.DP_BLUR}:hover, .${CLS.MEDIA_BLUR}:hover img, .${CLS.MSG_BLUR}:hover img { filter: none !important; } .${CLS.NAME_BLUR} { color: transparent; text-shadow: 0 0 5px rgba(0,0,0,0.6); user-select: none; cursor: pointer; transition: color 0.12s, text-shadow 0.12s; } .${CLS.NAME_BLUR}:hover { color: inherit; text-shadow: none; } `; document.head.appendChild(style); } // toggleClass function toggleClass(elements, className, condition) { elements.forEach(el => { if (el && el.classList) { condition ? el.classList.add(className) : el.classList.remove(className); } }); } // debounce function debounce(fn, delay) { let timeoutId; return function (...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn.apply(this, args), delay); }; } // getActiveMessageList function getActiveMessageList() { return ( document.querySelector('div[tabindex="-1"][role="region"]') || document.querySelector('div[aria-label="Message list"]') || document.querySelector('div[data-testid="chat-list"]') || document.querySelector('#main') ); } // blurContactsSidebar function blurContactsSidebar() { const pane = document.getElementById('pane-side'); if (!pane) return; toggleClass(Array.from(pane.querySelectorAll(SELECTORS.SIDEBAR_NAMES)), CLS.NAME_BLUR, true); toggleClass(Array.from(pane.querySelectorAll(SELECTORS.SIDEBAR_AVATARS)), CLS.DP_BLUR, true); } // blurChatMessages function blurChatMessages() { const messages = Array.from(document.querySelectorAll(SELECTORS.CHAT_MESSAGES)); const total = messages.length; messages.forEach((msg, idx) => { const shouldBlur = total > UNCENSOR_LAST_N && idx < total - UNCENSOR_LAST_N; Array.from( msg.querySelectorAll( [ SELECTORS.CHAT_MSG_TEXT, SELECTORS.CHAT_SPECIAL_SPANS, SELECTORS.CHAT_EMOJIS, SELECTORS.CHAT_MEDIA ].join(',') ) ).forEach(el => { el.classList.remove(CLS.MSG_BLUR); el.classList.remove(CLS.MEDIA_BLUR); }); if (shouldBlur) { toggleClass(Array.from(msg.querySelectorAll(SELECTORS.CHAT_MSG_TEXT)), CLS.MSG_BLUR, true); toggleClass(Array.from(msg.querySelectorAll(SELECTORS.CHAT_SPECIAL_SPANS)), CLS.MSG_BLUR, true); Array.from(msg.querySelectorAll(SELECTORS.CHAT_EMOJIS)).forEach(el => el.classList.add(CLS.MEDIA_BLUR)); } }); } // blurChatMedia function blurChatMedia() { const messages = Array.from(document.querySelectorAll(SELECTORS.CHAT_MESSAGES)); const total = messages.length; messages.forEach((msg, idx) => { const shouldBlur = total > UNCENSOR_LAST_N && idx < total - UNCENSOR_LAST_N; Array.from(msg.querySelectorAll(SELECTORS.CHAT_MEDIA)).forEach(el => { if (shouldBlur && !el.classList.contains('emoji') && !el.classList.contains(CLS.MEDIA_BLUR)) { el.classList.add(CLS.MEDIA_BLUR); } else if (!shouldBlur && !el.classList.contains('emoji')) { el.classList.remove(CLS.MEDIA_BLUR); } }); }); } // blurChatHeader function blurChatHeader() { const header = document.querySelector('#main header'); if (!header) return; toggleClass(Array.from(header.querySelectorAll(SELECTORS.CHAT_HEADER_NAMES)), CLS.NAME_BLUR, true); toggleClass(Array.from(header.querySelectorAll(SELECTORS.CHAT_HEADER_SPECIAL_SPANS)), CLS.NAME_BLUR, true); const avatarImg = header.querySelector(SELECTORS.CHAT_HEADER_AVATAR); if (avatarImg && avatarImg.classList) avatarImg.classList.add(CLS.DP_BLUR); const avatarDiv = header.querySelector(SELECTORS.CHAT_HEADER_DIV_AVATAR); if (avatarDiv && avatarDiv.classList) avatarDiv.classList.add(CLS.DP_BLUR); } // blurChatSideNames function blurChatSideNames() { toggleClass(Array.from(document.querySelectorAll(SELECTORS.CHAT_SIDE_NAMES)), CLS.NAME_BLUR, true); } // blurAllChatElements function blurAllChatElements() { blurChatMessages(); blurChatMedia(); blurChatHeader(); blurChatSideNames(); } const debouncedBlurAllChatElements = debounce(blurAllChatElements, 150); // observeContactSidebarChanges function observeContactSidebarChanges() { const pane = document.getElementById('pane-side'); if (!pane) return; blurContactsSidebar(); const observer = new MutationObserver(debounce(blurContactsSidebar, 100)); observer.observe(pane, { childList: true, subtree: true }); } // observeChatChanges function observeChatChanges() { const msgList = getActiveMessageList(); if (!msgList) return; blurAllChatElements(); const observer = new MutationObserver(() => { debouncedBlurAllChatElements(); }); observer.observe(msgList, { childList: true, subtree: true }); let scrollTimeout; msgList.addEventListener('scroll', () => { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { blurAllChatElements(); }, 250); }); msgList.addEventListener('mousemove', () => { setTimeout(() => blurAllChatElements(), 200); }); } // delayedBlurAllChatElements function delayedBlurAllChatElements(delay = 800) { setTimeout(blurAllChatElements, delay); } // setupObserversAndEvents function setupObserversAndEvents() { observeContactSidebarChanges(); observeChatChanges(); ['focus', 'mouseup', 'hashchange'].forEach(evt => { window.addEventListener(evt, () => delayedBlurAllChatElements(800)); }); document.addEventListener('visibilitychange', () => { if (!document.hidden) setTimeout(blurAllChatElements, 200); }); setInterval(() => { const anyUnblurred = !!document.querySelector( 'div.message-in span.selectable-text:not(.' + CLS.MSG_BLUR + '), ' + 'div.message-out span.selectable-text:not(.' + CLS.MSG_BLUR + ')' ); if (anyUnblurred) blurAllChatElements(); }, 2500); } // waitForElement function waitForElement(selector, root = document.body, timeout = 8000) { return new Promise((resolve, reject) => { const el = root.querySelector(selector); if (el) { resolve(el); return; } const observer = new MutationObserver((mutations, obs) => { const found = root.querySelector(selector); if (found) { obs.disconnect(); resolve(found); } }); observer.observe(root, { childList: true, subtree: true }); if (timeout) { setTimeout(() => { observer.disconnect(); console.warn(`Timeout waiting for selector "${selector}". Retrying...`); reject(new Error(`Timeout waiting for selector: ${selector}`)); }, timeout); } }); } // init async function init() { try { await waitForElement('#pane-side'); await waitForElement('#main'); setupObserversAndEvents(); } catch (error) { console.error('wwpb: Initialization failed:', error); setTimeout(init, 1000); } } init(); })();