您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A privacy-focused userscript that locally modifies Reddit chat to enhance anonymity by removing usernames and avatars
// ==UserScript== // @name Reddit Chat Anonymizer // @namespace Violentmonkey Scripts // @match https://chat.reddit.com/room* // @grant none // @version 1.0.2 // @author AveTrue // @license GNU General Public License v3.0 // @description A privacy-focused userscript that locally modifies Reddit chat to enhance anonymity by removing usernames and avatars // ==/UserScript== class RedditChatAnonymizer { constructor() { this.intervalId = null; this.isEnabled = localStorage.getItem('redditChatAnonymizerEnabled') !== 'false'; this.PLACEHOLDER_AVATAR = 'https://www.ledr.com/colours/black.jpg'; this.INSPECTION_INTERVAL = 50; // milliseconds this.INITIALIZATION_DELAY = 2000; // milliseconds } createToggleButton() { const button = document.createElement('button'); this.updateButtonState(button); const styles = { position: 'fixed', top: '10px', right: '100px', zIndex: '10000', padding: '8px 16px', backgroundColor: '#1a1a1b', color: '#d7dadc', border: '1px solid #343536', borderRadius: '4px', cursor: 'pointer', fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', fontSize: '14px' }; Object.assign(button.style, styles); button.addEventListener('click', () => this.toggleAnonymizer(button)); document.body.appendChild(button); } updateButtonState(button) { button.textContent = `Anonymizer: ${this.isEnabled ? 'ON' : 'OFF'}`; } toggleAnonymizer(button) { this.isEnabled = !this.isEnabled; localStorage.setItem('redditChatAnonymizerEnabled', this.isEnabled); this.updateButtonState(button); if (this.isEnabled) { this.startTimelineEventInspection(); } else { this.cleanup(); } } startTimelineEventInspection() { if (this.intervalId) clearInterval(this.intervalId); this.intervalId = setInterval(() => this.inspectTimelineEvents(), this.INSPECTION_INTERVAL); } cleanup() { if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; window.location.reload(); } } getCurrentUsername() { try { // Try multiple selector patterns for current username const rsApp = document.querySelector('rs-app'); if (rsApp) { const curUser = rsApp.querySelector('rs-current-user'); if (curUser) { return curUser.getAttribute('display-name'); } } // Try new structure const userElement = document.querySelector('.current-user-name') || document.querySelector('.username-display'); return userElement?.textContent?.trim(); } catch (error) { console.debug('Error getting current username:', error); return null; } } getTimelineEvents() { const rsApp = document.querySelector('rs-app'); const overlayManager = rsApp?.shadowRoot?.querySelector('rs-room-overlay-manager'); const rsRoom = overlayManager?.querySelector('rs-room'); const timeline = rsRoom?.shadowRoot?.querySelector('rs-timeline'); const virtualScroll = timeline?.shadowRoot?.querySelector('rs-virtual-scroll-dynamic'); return virtualScroll?.shadowRoot?.querySelectorAll('rs-timeline-event') || []; } anonymizeAvatar(avatar) { if (!avatar) return; avatar.src = this.PLACEHOLDER_AVATAR; avatar.setAttribute('href', this.PLACEHOLDER_AVATAR); avatar.onload = () => avatar.dispatchEvent(new CustomEvent('render', { bubbles: true })); } anonymizeEvent(event, currentUsername) { try { // First try the old structure let nameSpan = event.shadowRoot.querySelector('rs-timeline-event-hovercard-display-name span'); // If that fails, try the new structure if (!nameSpan) { const messageContainer = event.shadowRoot.querySelector('.message-container'); if (messageContainer) { nameSpan = messageContainer.querySelector('.username-text'); } } // If we found a name span and it's not the current user if (nameSpan && nameSpan.textContent !== currentUsername) { nameSpan.textContent = ''; // Try multiple avatar selector patterns const avatar = event.shadowRoot.querySelector('.avatar-image') || event.shadowRoot.querySelector('image') || event.shadowRoot.querySelector('img') || event.shadowRoot.querySelector('.user-avatar img'); this.anonymizeAvatar(avatar); } } catch (error) { console.debug('Error in anonymizeEvent:', error, '\nEvent:', event, '\nShadowRoot content:', event.shadowRoot?.innerHTML); } } inspectTimelineEvents() { if (!this.isEnabled) return; const currentUsername = this.getCurrentUsername(); const timelineEvents = this.getTimelineEvents(); timelineEvents.forEach(event => { this.anonymizeEvent(event, currentUsername); }); } initialize() { setTimeout(() => { this.createToggleButton(); if (this.isEnabled) this.startTimelineEventInspection(); }, this.INITIALIZATION_DELAY); } } // Initialize the anonymizer const anonymizer = new RedditChatAnonymizer(); anonymizer.initialize();