您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Blurt Wörter auf Webseiten. Features: Mehrere Treffer, IP-Erkennung, Vollbild und Ausnahmen
// ==UserScript== // @name Wörter-Verwischen-Skript // @namespace http://tampermonkey.net/ // @version 3.37 // @description Blurt Wörter auf Webseiten. Features: Mehrere Treffer, IP-Erkennung, Vollbild und Ausnahmen // @author Sky95 // @match *://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_xmlhttpRequest // @connect api.ipify.org // @run-at document-start // @noframes // ==/UserScript== (function() { 'use strict'; let defaultFullPageBlurCheck = GM_getValue('defaultFullPageBlur', false); let initialPageBlurStrength = GM_getValue('pageBlurStrength', 10); let applyForceInitialBlur = defaultFullPageBlurCheck; let exceptionListEarly = GM_getValue('exceptionList', []); let earlyMatch = null; try { const currentUrl = window.location.href; const currentHostname = new URL(currentUrl).hostname; for (const exception of exceptionListEarly) { if (!exception || typeof exception.pattern !== 'string' || typeof exception.mode !== 'string') continue; const pattern = exception.pattern; if (pattern.endsWith('/*')) { const domain = pattern.slice(0, -2); if (currentHostname === domain || currentHostname.endsWith('.' + domain)) { earlyMatch = exception; break; } } else { const regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*')); if (regex.test(currentUrl)) { earlyMatch = exception; break; } } } } catch(e) { } if (earlyMatch && earlyMatch.mode === 'no_blur') { applyForceInitialBlur = false; } if (applyForceInitialBlur) { const style = document.createElement('style'); style.id = 'blur-force-initial-style'; style.textContent = ` html.force-initial-blur { filter: blur(${initialPageBlurStrength}px) !important; transition: none !important; } html.force-initial-blur * { pointer-events: none !important; } `; if (document.documentElement) { document.documentElement.appendChild(style); document.documentElement.classList.add('force-initial-blur'); } else { document.addEventListener('DOMContentLoaded', () => { if(document.documentElement) { document.documentElement.appendChild(style); document.documentElement.classList.add('force-initial-blur'); } }, { once: true }); } } GM_addStyle(` .word-blur { filter: blur(7.5px); transition: filter 1.5s ease; } .word-blur:hover { filter: blur(0px); transition-delay: 1s; } html.full-page-blur { filter: blur(10px) !important; transition: none !important; } html.full-page-unblur { filter: blur(0px) !important; transition: filter 0.5s ease 0.5s !important; } html.full-page-blur * { pointer-events: none !important; } body { max-width: 100% !important; overflow-x: hidden !important; } #blur-settings-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 550px; background: #2b2b2b !important; color: #fff !important; border-radius: 8px !important; box-shadow: 0 4px 20px rgba(0,0,0,0.8) !important; z-index: 10000; display: none; font-family: 'Courier New', monospace !important; pointer-events: auto !important; opacity: 0; transition: opacity 0.3s ease-in-out, transform 0.3s ease-out; } #blur-settings-panel.visible { opacity: 0.98; } #blur-section-container { max-height: 75vh !important; overflow-y: auto !important; scrollbar-width: thin !important; scrollbar-color: #666 #2b2b2b !important; } #blur-settings-header { padding: 15px 20px !important; background: #363636 !important; border-radius: 8px 8px 0 0; cursor: move; user-select: none; display: flex; justify-content: space-between; align-items: center; } #blur-settings-header h3 { color: #fff !important; font-size: 18px !important; font-weight: normal !important; font-family: Arial, sans-serif !important; margin: 0 !important; padding: 0 !important; } #blur-text-info, #blur-exception-info { padding: 8px 15px !important; margin-bottom: 10px !important; background: #262626 !important; border: 1px solid #406040 !important; font-size: 12px !important; color: #afa !important; line-height: 1.4 !important; border-radius: 4px; } #blur-exception-info { border-color: #604060 !important; color: #aaf !important; } #blur-ip-info { padding: 5px 20px !important; background: #262626 !important; font-size: 14px !important; color: #aaf !important; display: flex !important; justify-content: space-between !important; align-items: center !important; pointer-events: auto !important; filter: blur(0) !important; margin-top: 5px !important; border-radius: 4px; } #blur-ip-display { pointer-events: auto !important; filter: blur(0) !important; } #blur-ip-button { background: #4CAF50 !important; color: white !important; border: none !important; border-radius: 4px !important; padding: 5px 10px !important; cursor: pointer !important; font-size: 12px !important; pointer-events: auto !important; filter: blur(0) !important; } #blur-ip-button:hover { background: #388E3C !important; } #blur-close-button { background: none !important; border: none !important; color: #fff !important; cursor: pointer !important; font-family: Arial, sans-serif !important; font-size: 24px !important; padding: 0 !important; width: 24px !important; height: 24px !important; display: flex !important; align-items: center !important; justify-content: center !important; line-height: 1 !important; text-decoration: none !important; outline: none !important; } #blur-close-button:hover { background: rgba(255,255,255,0.1) !important; border-radius: 4px !important; } .blur-section-content { padding: 15px 20px !important; display: none; background: #2b2b2b !important; } .blur-button-container { padding: 15px 20px !important; background: #363636 !important; border-top: 1px solid #404040 !important; display: flex !important; justify-content: space-between !important; border-radius: 0 0 8px 8px !important; gap: 10px !important; } .blur-button { padding: 8px 16px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important; font-family: 'Courier New', monospace !important; font-size: 14px !important; outline: none !important; width: auto !important; } .blur-save-button { background: #2196F3 !important; color: white !important; } .blur-save-button:hover { background: #1976D2 !important; } .blur-cancel-button { background: #666 !important; color: white !important; } .blur-cancel-button:hover { background: #555 !important; } .blur-section { margin: 5px 0 !important; border-bottom: 1px solid #404040 !important; background: #2b2b2b !important; } .blur-section:last-of-type { border-bottom: none !important; } .blur-section-header { padding: 10px 20px !important; background: #363636 !important; cursor: pointer !important; user-select: none !important; display: flex !important; justify-content: space-between !important; align-items: center !important; color: #fff !important; font-size: 14px !important; font-weight: bold !important; } .blur-section-content { padding: 15px 20px !important; display: none; background: #2b2b2b !important; } .blur-section.expanded .blur-section-content { display: block; } .blur-section-arrow { transition: transform 0.3s ease; color: #fff !important; font-size: 12px !important; } .blur-section.expanded .blur-section-arrow { transform: rotate(180deg); } .blur-slider-container { margin-top: 10px !important; display: flex !important; align-items: center !important; gap: 5px !important; } .blur-slider-label { flex: 1 !important; color: #fff !important; font-size: 14px !important; min-width: 150px; } .blur-slider { flex: 1 !important; height: 4px !important; max-width: 125px; background: #666 !important; outline: none !important; -webkit-appearance: none !important; appearance: none !important; border-radius: 2px !important; cursor: pointer; } .blur-slider::-webkit-slider-thumb { -webkit-appearance: none !important; appearance: none !important; width: 16px !important; height: 16px !important; background: #2196F3 !important; border-radius: 50% !important; cursor: pointer !important; } .blur-slider::-moz-range-thumb { width: 16px !important; height: 16px !important; background: #2196F3 !important; border-radius: 50% !important; cursor: pointer !important; border: none !important; } .blur-slider-value { min-width: 45px !important; text-align: right !important; color: #2196F3 !important; font-size: 12px !important; } #blur-toggle-button { position: fixed !important; top: 0 !important; left: 0 !important; width: 14px !important; height: 14px !important; max-width: 14px !important; max-height: 14px !important; min-width: 14px !important; min-height: 14px !important; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACY0lEQVQ4jY3VP4gVVxQG8N+dN+tz1dVVWRMwiQkSEkTBSgurpEqXQkwsrSWQKo2NhYE06dJIugRRk0qIovgHBcHEpAmYgCYQ/LO7havo+nZ1n/v23hT3Ljs8H7t+cGcuw3zfnHPu+c6Ek5bFQRzFG+ji38S5IX6Z5b9DdEN+Ly0S6uX1HMaHZT+DscDWBUbX0j5P+plqHXGG3mfElQTfaexXY2Nga+KtyJOnpMTqx7xoM3WVZ639S4Tv8VWJ+gH24MuGYCjpLVTMY1NiJ3ZXvBl52aETSg0/x+kGea5E1ETCAqYxiQ7aGMJ44HLiSl2IP/SR+8XgOVoYCWxPxEAVUTEa6bR4WONY+dJK+Auj2JJYhzoh5DquD7wd+KDC/RWEvsGOwElcTNwJOd0oC86X9B9FntX4Tj6En7CqT+xbHCnEhcD2mOu4AWtKlE/xd+J64GZViGewb0B0Xy9uphnHP7ideIxeYD4whT9rfm9zp2qQ4wDBNODZQPTKfbEPP8UFrzqnxiUY5r0e7wf2BnZhs9wyQb50Mdfazxf4cYAYuQyrMIFPsCfktU2uYStk0bVop2K9d1fI5khZv4VsvbHECCpIWXAksDmyIZxguMon1X/C/ZiVG7sKuWTRUmM/ivwaOFvjBQ7JfbaIrlebfY1ivcRkoJNoB4YiExU3e9yqq/ypUxUfY4dswzOy8a80FUP2+L3EtcRdDMfskoeJP7o8CCcahGYPFdyzNMJ6cv/dwOmKW7GMr6Gc5dRYccpyaArO4Yk8WcYrJtczN9MYsB+9xoA9ji1ldYsrJhLTs3QP8LL/F/A/amq/lc3UweoAAAAASUVORK5CYII=') !important; background-size: cover !important; background-color: transparent !important; background-repeat: no-repeat !important; border: none !important; cursor: pointer !important; z-index: 9999 !important; padding: 0 !important; margin: 0 !important; outline: none !important; pointer-events: auto !important; filter: blur(0) !important; } #blur-toggle-button:hover { opacity: 0.8 !important; } #blur-word-list, #blur-exception-list { display: flex !important; flex-direction: column !important; margin: 0 0 10px 0 !important; padding: 0 !important; gap: 8px; } .blur-list-entry { display: flex !important; gap: 5px !important; align-items: center !important; background-color: #333; padding: 5px; border-radius: 4px; } .blur-list-input { flex-grow: 1 !important; background: #1e1e1e !important; border: 1px solid #404040 !important; color: #ffffff !important; padding: 8px !important; border-radius: 4px !important; font-family: 'Courier New', monospace !important; font-size: 14px !important; line-height: 1.4 !important; height: auto !important; width: auto !important; margin: 0 !important; outline: none !important; box-shadow: none !important; -webkit-appearance: none !important; -moz-appearance: none !important; appearance: none !important; transition: border-color 0.2s ease, filter 0.2s ease !important; filter: blur(2px) !important; } .blur-list-input:focus { border-color: #2196F3 !important; filter: blur(0) !important; } .blur-delete-button { background: #ff4444 !important; color: white !important; border: none !important; border-radius: 4px !important; width: 30px !important; height: 30px !important; cursor: pointer !important; display: flex !important; align-items: center !important; justify-content: center !important; font-family: Arial, sans-serif !important; font-size: 20px !important; line-height: 1 !important; padding: 0 !important; margin: 0 !important; outline: none !important; transition: background-color 0.2s ease !important; flex-shrink: 0; } .blur-delete-button:hover { background: #cc0000 !important; } .blur-exception-mode-select { background: #444 !important; color: #fff !important; border: 1px solid #666 !important; border-radius: 4px !important; padding: 5px 8px !important; font-family: Arial, sans-serif !important; font-size: 12px !important; height: 30px !important; outline: none !important; cursor: pointer; flex-shrink: 0; min-width: 100px; } .blur-add-button { background: #4CAF50 !important; color: white !important; border: none !important; border-radius: 4px !important; padding: 8px !important; height: 36px !important; cursor: pointer !important; width: 100% !important; margin-top: 5px !important; font-family: 'Courier New', monospace !important; font-size: 14px !important; text-align: center !important; outline: none !important; transition: background-color 0.2s ease !important; } .blur-add-current-button { background: #007bff !important; color: white !important; margin-top: 10px !important; margin-bottom: 5px !important; } .blur-add-button:hover, .blur-add-current-button:hover { filter: brightness(0.9); } .blur-option-container { padding: 10px !important; background: #1e1e1e !important; border-radius: 4px !important; margin-top: 10px !important; } .blur-checkbox-label { display: flex !important; align-items: center !important; gap: 10px !important; color: #fff !important; font-size: 14px !important; cursor: pointer !important; user-select: none !important; padding: 5px; border-radius: 3px; transition: background-color 0.2s ease; } .blur-checkbox-label:hover { background-color: rgba(255, 255, 255, 0.05); } .blur-checkbox { width: 16px !important; height: 16px !important; cursor: pointer !important; margin: 0 !important; flex-shrink: 0; bottom: 0px !important; } `); let blurWords = GM_getValue('blurWords', []); let exceptionList = GM_getValue('exceptionList', []); let userIP = GM_getValue('userIP', ''); let isFullPageBlurred = false; let defaultFullPageBlur = GM_getValue('defaultFullPageBlur', false); let autoUnblurAfterProcessing = GM_getValue('autoUnblurAfterProcessing', true); let wordBlurStrength = GM_getValue('wordBlurStrength', 7.5); let hoverDelay = GM_getValue('hoverDelay', 1.0); let pageBlurStrength = GM_getValue('pageBlurStrength', 10); let pageUnblurDelay = GM_getValue('pageUnblurDelay', 0.5); let pageUnblurSpeed = GM_getValue('pageUnblurSpeed', 0.5); let wordunblurTransitionSpeed = GM_getValue('wordunblurTransitionSpeed', 1.5); let isIPBlurEnabled = GM_getValue('isIPBlurEnabled', false); let clickTimeout = null; const processedNodes = new WeakSet(); let pageMatchesException = null; let shouldBlurWordsOnPage = true; let shouldInitialBlur = false; let manualUnblurListenerActive = false; let panelElement = null; let xOffset = 0; let yOffset = 0; const BATCH_SIZE = 20; let initialVisibleScanComplete = false; let deferredNodes = []; let processingActive = false; function findMatchingException(url) { const currentHostname = new URL(url).hostname; for (const exception of exceptionList) { if (!exception || typeof exception.pattern !== 'string' || typeof exception.mode !== 'string') continue; try { const pattern = exception.pattern; if (pattern.endsWith('/*')) { const domain = pattern.slice(0, -2); if (currentHostname === domain || currentHostname.endsWith('.' + domain)) { return exception; } } else { const regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*')); if (regex.test(url)) { return exception; } } } catch (e) { } } return null; } function determineBlurBehavior() { pageMatchesException = findMatchingException(window.location.href); shouldBlurWordsOnPage = true; shouldInitialBlur = defaultFullPageBlur; if (pageMatchesException) { switch (pageMatchesException.mode) { case 'no_blur': shouldBlurWordsOnPage = false; shouldInitialBlur = false; break; case 'click_unblur': shouldBlurWordsOnPage = true; shouldInitialBlur = true; break; } } else { shouldBlurWordsOnPage = true; shouldInitialBlur = defaultFullPageBlur; } } function removeFullPageBlurClass(useTransition = true) { document.documentElement.classList.remove('force-initial-blur'); if (useTransition) { document.documentElement.classList.add('full-page-unblur'); } document.documentElement.classList.remove('full-page-blur'); } function applyFullPageBlurClass() { document.documentElement.classList.remove('force-initial-blur'); document.documentElement.classList.remove('full-page-unblur'); document.documentElement.classList.add('full-page-blur'); } function removeFullPageBlur() { if (!isFullPageBlurred) return; isFullPageBlurred = false; setTimeout(() => { removeFullPageBlurClass(true); }, 50); removeManualUnblurListener(); } function applyFullPageBlur() { if (isFullPageBlurred) return; isFullPageBlurred = true; applyFullPageBlurClass(); } const manualUnblurHandler = () => { removeFullPageBlur(); }; function addManualUnblurListener() { if (manualUnblurListenerActive || !isFullPageBlurred) return; manualUnblurListenerActive = true; document.documentElement.addEventListener('click', manualUnblurHandler, { capture: true, once: true }); document.documentElement.addEventListener('keydown', manualUnblurHandler, { capture: true, once: true }); } function removeManualUnblurListener() { if (!manualUnblurListenerActive) return; manualUnblurListenerActive = false; document.documentElement.removeEventListener('click', manualUnblurHandler, { capture: true, once: true }); document.documentElement.removeEventListener('keydown', manualUnblurHandler, { capture: true, once: true }); } function checkAndApplyInitialBlurState() { if (shouldInitialBlur) { applyFullPageBlurClass(); isFullPageBlurred = true; } else { removeFullPageBlurClass(false); isFullPageBlurred = false; } } function checkAndHandlePostProcessingBlurState() { if (!isFullPageBlurred) return; let requiresManualUnblur = false; if (pageMatchesException) { if (pageMatchesException.mode === 'click_unblur') { requiresManualUnblur = true; } else if (pageMatchesException.mode === 'no_blur'){ removeFullPageBlur(); return; } } if (!requiresManualUnblur && autoUnblurAfterProcessing) { removeFullPageBlur(); } else if (isFullPageBlurred) { addManualUnblurListener(); } } function signalProcessingComplete() { if (!initialVisibleScanComplete) { initialVisibleScanComplete = true; checkAndHandlePostProcessingBlurState(); if (deferredNodes.length > 0) { const nodesToProcessNow = [...deferredNodes]; deferredNodes = []; processNodes(document.body, false, nodesToProcessNow); } activateDelayedFunctionality(); } } function toggleFullPageBlur() { if (isFullPageBlurred) { removeFullPageBlur(); } else { if (pageMatchesException && pageMatchesException.mode === 'no_blur') return; applyFullPageBlur(); addManualUnblurListener(); } } function updateBlurStyles() { GM_addStyle(` .word-blur { filter: blur(${wordBlurStrength}px) !important; transition: filter ${wordunblurTransitionSpeed}s ease !important; } .word-blur:hover { filter: blur(0px) !important; transition-delay: ${hoverDelay}s !important; } html.full-page-blur { filter: blur(${pageBlurStrength}px) !important; transition: none !important; } html.full-page-unblur { filter: blur(0px) !important; transition: filter ${pageUnblurSpeed}s ease ${pageUnblurDelay}s !important; } `); } function fetchIP() { const ipDisplay = document.getElementById('blur-ip-display'); if (ipDisplay) { ipDisplay.style.filter = 'blur(0)'; ipDisplay.style.opacity = '0.5'; ipDisplay.textContent = 'Ermittle IP...'; } GM_xmlhttpRequest({ method: 'GET', url: 'https://api.ipify.org?format=json', timeout: 5000, onload: function(response) { let newIP = null; try { const data = JSON.parse(response.responseText); if (data.ip) { newIP = data.ip; } } catch (e) { } finally { if (newIP) { userIP = newIP; GM_setValue('userIP', userIP); if (ipDisplay) { ipDisplay.textContent = `Aktuelle IP: ${userIP}`; ipDisplay.style.opacity = '1'; ipDisplay.style.color = '#aaf'; } } else { if (ipDisplay) { ipDisplay.textContent = `IP-Ermittlung fehlgeschlagen. (${userIP ? 'Gespeichert: ' + userIP : 'Keine gespeichert'})`; ipDisplay.style.opacity = '1'; ipDisplay.style.color = '#f88'; } } } }, onerror: function(error) { if (ipDisplay) { ipDisplay.textContent = `IP-Anfrage fehlgeschlagen. (${userIP ? 'Gespeichert: ' + userIP : 'Keine gespeichert'})`; ipDisplay.style.opacity = '1'; ipDisplay.style.color = '#f88'; } }, ontimeout: function() { if (ipDisplay) { ipDisplay.textContent = `IP-Anfrage Zeitüberschreitung. (${userIP ? 'Gespeichert: ' + userIP : 'Keine gespeichert'})`; ipDisplay.style.opacity = '1'; ipDisplay.style.color = '#f88'; } } }); } function createWordListEntry(value = '') { const entry = document.createElement('div'); entry.className = 'blur-list-entry'; const input = document.createElement('input'); input.type = 'text'; input.className = 'blur-list-input'; input.value = value; input.placeholder = 'Wort oder Ausdruck'; const deleteBtn = document.createElement('button'); deleteBtn.className = 'blur-delete-button'; deleteBtn.innerHTML = '×'; deleteBtn.onclick = () => entry.remove(); entry.appendChild(input); entry.appendChild(deleteBtn); return entry; } function createExceptionListEntry(exception = { pattern: '', mode: 'no_blur' }) { const entry = document.createElement('div'); entry.className = 'blur-list-entry'; const input = document.createElement('input'); input.type = 'text'; input.className = 'blur-list-input'; input.value = exception.pattern; input.placeholder = 'z.B. domain.com/* oder https://seite.de/pfad*'; const select = document.createElement('select'); select.className = 'blur-exception-mode-select'; select.innerHTML = ` <option value="no_blur" ${exception.mode === 'no_blur' ? 'selected' : ''}>Nicht Bluren</option> <option value="click_unblur" ${exception.mode === 'click_unblur' ? 'selected' : ''}>Geblurt lassen</option> `; const deleteBtn = document.createElement('button'); deleteBtn.className = 'blur-delete-button'; deleteBtn.innerHTML = '×'; deleteBtn.onclick = () => entry.remove(); entry.appendChild(input); entry.appendChild(select); entry.appendChild(deleteBtn); return entry; } function adjustPanelPosition() { if (!panelElement) return; const panelRect = panelElement.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const margin = 10; let needsAdjustment = false; let targetX = xOffset; let targetY = yOffset; if (panelRect.right > viewportWidth - margin) { targetX = xOffset - (panelRect.right - (viewportWidth - margin)); needsAdjustment = true; } if (panelRect.left < margin) { targetX = xOffset + (margin - panelRect.left); needsAdjustment = true; } if (panelRect.bottom > viewportHeight - margin) { targetY = yOffset - (panelRect.bottom - (viewportHeight - margin)); needsAdjustment = true; } if (panelRect.top < margin) { targetY = yOffset + (margin - panelRect.top); needsAdjustment = true; } if (panelRect.right < 0 || panelRect.left > viewportWidth || panelRect.bottom < 0 || panelRect.top > viewportHeight) { targetX = 0; targetY = 0; needsAdjustment = true; } if (needsAdjustment) { panelElement.style.transition = 'transform 0.3s ease-out'; xOffset = targetX; yOffset = targetY; panelElement.style.transform = `translate(calc(-50% + ${xOffset}px), calc(-50% + ${yOffset}px))`; setTimeout(() => { if (panelElement) panelElement.style.transition = 'opacity 0.3s ease-in-out, transform 0.3s ease-out'; }, 300); } } function createSettings() { panelElement = document.createElement('div'); panelElement.id = 'blur-settings-panel'; const version = GM_info.script.version; panelElement.innerHTML = ` <div id="blur-settings-header"> <h3 style="margin:0">Wörter-Verwischen Einstellungen (v${version})</h3> <button id="blur-close-button">×</button> </div> <div id="blur-section-container"> <div id="blur-ip-info"> Tipp: Mache ein Doppelkick auf das Einstellungs-Symbol um die Seite zu Bluren. Mit Escape kannst du das Bluren der Seite und das UI wieder Schließen. </div> <div class="blur-section"> <div class="blur-section-header"> <span>Allgemeine Einstellungen</span> <span class="blur-section-arrow">▼</span> </div> <div class="blur-section-content"> <div id="blur-text-info"> Passe hier die Stärke und das Verhalten des Blurs an. </div> <div class="blur-slider-container"> <label class="blur-slider-label">Wort Blur-Stärke (5px - 10px)</label> <input type="range" min="5" max="10" step="0.5" value="${wordBlurStrength}" class="blur-slider" id="word-blur-slider"> <span class="blur-slider-value">${wordBlurStrength.toFixed(1)}px</span> </div> <div class="blur-slider-container"> <label class="blur-slider-label">Hover Verzögerung (0.5s - 1.5s)</label> <input type="range" min="0.5" max="1.5" step="0.1" value="${hoverDelay}" class="blur-slider" id="hover-delay-slider"> <span class="blur-slider-value">${hoverDelay.toFixed(1)}s</span> </div> <div class="blur-slider-container"> <label class="blur-slider-label">Wort Unblur-Zeit (0.5s - 2.5s)</label> <input type="range" min="0.5" max="2.5" step="0.1" value="${wordunblurTransitionSpeed}" class="blur-slider" id="transition-slider"> <span class="blur-slider-value">${wordunblurTransitionSpeed.toFixed(1)}s</span> </div> <div class="blur-option-container"> <label class="blur-checkbox-label"> <input type="checkbox" class="blur-checkbox" id="blur-default-setting" ${defaultFullPageBlur ? 'checked' : ''}> Seite standardmäßig beim Start blurren </label> </div> <div class="blur-option-container"> <label class="blur-checkbox-label"> <input type="checkbox" class="blur-checkbox" id="blur-auto-unblur-setting" ${autoUnblurAfterProcessing ? 'checked' : ''}> Seite nach Verarbeiten automatisch entbluren </label> </div> <div class="blur-slider-container"> <label class="blur-slider-label">Seiten Blur-Stärke (5px - 15px)</label> <input type="range" min="5" max="15" step="0.5" value="${pageBlurStrength}" class="blur-slider" id="page-blur-slider"> <span class="blur-slider-value">${pageBlurStrength.toFixed(1)}px</span> </div> <div class="blur-slider-container"> <label class="blur-slider-label">Seiten Unblur-Verzögerung (0.0s - 1.0s)</label> <input type="range" min="0.0" max="1.0" step="0.1" value="${pageUnblurDelay}" class="blur-slider" id="page-unblur-delay-slider"> <span class="blur-slider-value">${pageUnblurDelay.toFixed(1)}s</span> </div> <div class="blur-slider-container"> <label class="blur-slider-label">Seiten Unblur-Geschw. (0.0s - 1.0s)</label> <input type="range" min="0.0" max="1.0" step="0.1" value="${pageUnblurSpeed}" class="blur-slider" id="page-unblur-speed-slider"> <span class="blur-slider-value">${pageUnblurSpeed.toFixed(1)}s</span> </div> </div> </div> <div class="blur-section"> <div class="blur-section-header"> <span>Ausnahme-Einstellungen</span> <span class="blur-section-arrow">▼</span> </div> <div class="blur-section-content"> <div id="blur-exception-info"> Definiere Seiten (Domains oder URLs mit *), die speziell behandelt werden sollen. Wähle pro Eintrag, ob die Seite gar nicht oder nur initial geblurrt werden soll (Klick/Taste zum Aufheben). </div> <button class="blur-add-button blur-add-current-button" id="blur-add-current-site">Aktuelle Domain hinzufügen (domain.com/*)</button> <div id="blur-exception-list"></div> <button class="blur-add-button" id="blur-add-exception">+ Neue Ausnahme hinzufügen</button> </div> </div> <div class="blur-section"> <div class="blur-section-header"> <span>IP-Einstellungen</span> <span class="blur-section-arrow">▼</span> </div> <div class="blur-section-content"> <div id="blur-text-info"> Ermittel erst deine IP und aktiviere die Option, wenn du sie automatisch mitblurren möchtest.<br>Die Ermittlung muss manuell angestoßen werden! </div> <div id="blur-ip-info"> <span id="blur-ip-display">Aktuelle IP: ${userIP || 'Nicht ermittelt'}</span> <button id="blur-ip-button">IP ermitteln</button> </div> <div class="blur-option-container"> <label class="blur-checkbox-label"> <input type="checkbox" class="blur-checkbox" id="ip-blur-setting" ${isIPBlurEnabled ? 'checked' : ''}> Ermittelte IP automatisch mitblurren </label> </div> </div> </div> <div class="blur-section"> <div class="blur-section-header"> <span>Wortliste</span> <span class="blur-section-arrow">▼</span> </div> <div class="blur-section-content"> <div id="blur-text-info"> Füge hier Wörter oder Ausdrücke hinzu, die geblurrt werden sollen.<br>Nur ein Eintrag pro Feld! Groß-/Kleinschreibung wird ignoriert. </div> <div id="blur-word-list"></div> <button class="blur-add-button" id="blur-add-word">+ Neues Wort hinzufügen</button> </div> </div> </div> <div class="blur-button-container"> <button class="blur-button blur-cancel-button" id="blur-cancel">Abbrechen</button> <button class="blur-button blur-save-button" id="blur-save">Speichern & Neu laden</button> </div> `; document.body.appendChild(panelElement); const wordListContainer = panelElement.querySelector('#blur-word-list'); const exceptionListContainer = panelElement.querySelector('#blur-exception-list'); const addWordButton = panelElement.querySelector('#blur-add-word'); const addExceptionButton = panelElement.querySelector('#blur-add-exception'); const addCurrentSiteButton = panelElement.querySelector('#blur-add-current-site'); const ipButton = panelElement.querySelector('#blur-ip-button'); const sections = panelElement.querySelectorAll('.blur-section'); sections.forEach(section => { const header = section.querySelector('.blur-section-header'); header.addEventListener('click', () => { const wasExpanded = section.classList.contains('expanded'); if (!wasExpanded) { section.classList.add('expanded'); } else { section.classList.remove('expanded'); } }); }); const sliders = panelElement.querySelectorAll('.blur-slider'); sliders.forEach(slider => { const valueDisplay = slider.nextElementSibling; const isTime = slider.id.includes('delay') || slider.id.includes('transition') || slider.id.includes('speed'); const unit = isTime ? 's' : 'px'; slider.addEventListener('input', () => { valueDisplay.textContent = `${parseFloat(slider.value).toFixed(1)}${unit}`; }); valueDisplay.textContent = `${parseFloat(slider.value).toFixed(1)}${unit}`; }); addWordButton.onclick = () => { wordListContainer.appendChild(createWordListEntry()); wordListContainer.lastElementChild?.querySelector('input')?.focus(); }; addExceptionButton.onclick = () => { exceptionListContainer.appendChild(createExceptionListEntry()); exceptionListContainer.lastElementChild?.querySelector('input')?.focus(); }; addCurrentSiteButton.onclick = () => { const hostname = window.location.hostname; if (hostname) { const pattern = `${hostname}/*`; const inputs = exceptionListContainer.querySelectorAll('.blur-list-input'); const exists = Array.from(inputs).some(input => input.value.trim() === pattern); if (!exists) { exceptionListContainer.appendChild(createExceptionListEntry({ pattern: pattern, mode: 'no_blur' })); exceptionListContainer.lastElementChild?.querySelector('input')?.focus(); } else { } } }; ipButton.onclick = fetchIP; const toggleBtn = document.createElement('button'); toggleBtn.id = 'blur-toggle-button'; document.body.appendChild(toggleBtn); let isDragging = false; let currentX, currentY, initialX, initialY; const dragStart = (e) => { if (e.target.closest('#blur-settings-header')) { initialX = e.clientX - xOffset; initialY = e.clientY - yOffset; isDragging = true; if (panelElement) panelElement.style.transition = 'none'; } }; const drag = (e) => { if (isDragging && panelElement) { e.preventDefault(); currentX = e.clientX - initialX; currentY = e.clientY - initialY; xOffset = currentX; yOffset = currentY; panelElement.style.transform = `translate(calc(-50% + ${xOffset}px), calc(-50% + ${yOffset}px))`; } }; const dragEnd = () => { if (isDragging) { isDragging = false; if (panelElement) panelElement.style.transition = 'opacity 0.3s ease-in-out, transform 0.3s ease-out'; adjustPanelPosition(); } }; window.addEventListener('resize', adjustPanelPosition); document.addEventListener('mousedown', dragStart, false); document.addEventListener('mousemove', drag, false); document.addEventListener('mouseup', dragEnd, false); document.addEventListener('mouseleave', dragEnd, false); window.showPanel = function showPanel() { if (!panelElement) return; panelElement.style.display = 'block'; panelElement.style.transform = `translate(calc(-50% + ${xOffset}px), calc(-50% + ${yOffset}px))`; setTimeout(() => panelElement.classList.add('visible'), 10); wordListContainer.innerHTML = ''; blurWords.forEach(word => wordListContainer.appendChild(createWordListEntry(word))); exceptionListContainer.innerHTML = ''; exceptionList.forEach(exception => exceptionListContainer.appendChild(createExceptionListEntry(exception))); const ipDisplay = panelElement.querySelector('#blur-ip-display'); if (ipDisplay) ipDisplay.textContent = `Aktuelle IP: ${userIP || 'Nicht ermittelt'}`; const autoUnblurCheckbox = panelElement.querySelector('#blur-auto-unblur-setting'); if (autoUnblurCheckbox) autoUnblurCheckbox.checked = autoUnblurAfterProcessing; const defaultBlurCheckbox = panelElement.querySelector('#blur-default-setting'); if (defaultBlurCheckbox) defaultBlurCheckbox.checked = defaultFullPageBlur; } window.hidePanel = function hidePanel() { if (!panelElement) return; panelElement.classList.remove('visible'); setTimeout(() => panelElement.style.display = 'none', 300); } function handleClick() { if (clickTimeout !== null) { clearTimeout(clickTimeout); clickTimeout = null; toggleFullPageBlur(); } else { clickTimeout = setTimeout(() => { clickTimeout = null; window.showPanel(); }, 250); } } function saveSettings() { if (!panelElement) return; const wordInputs = panelElement.querySelectorAll('#blur-word-list .blur-list-input'); const newWords = [...new Set([...wordInputs].map(input => input.value.trim()).filter(w => w))]; const newExceptions = []; const exceptionEntries = panelElement.querySelectorAll('#blur-exception-list .blur-list-entry'); exceptionEntries.forEach(entry => { const patternInput = entry.querySelector('.blur-list-input'); const modeSelect = entry.querySelector('.blur-exception-mode-select'); const pattern = patternInput?.value.trim(); const mode = modeSelect?.value; if (pattern && mode) { newExceptions.push({ pattern: pattern, mode: mode }); } }); const pageBlurSlider = panelElement.querySelector('#page-blur-slider'); const wordBlurSlider = panelElement.querySelector('#word-blur-slider'); const hoverDelaySlider = panelElement.querySelector('#hover-delay-slider'); const transitionSlider = panelElement.querySelector('#transition-slider'); const pageUnblurDelaySlider = panelElement.querySelector('#page-unblur-delay-slider'); const pageUnblurSpeedSlider = panelElement.querySelector('#page-unblur-speed-slider'); const defaultBlurCheckbox = panelElement.querySelector('#blur-default-setting'); const autoUnblurCheckbox = panelElement.querySelector('#blur-auto-unblur-setting'); const ipBlurCheckbox = panelElement.querySelector('#ip-blur-setting'); pageBlurStrength = parseFloat(pageBlurSlider.value); wordBlurStrength = parseFloat(wordBlurSlider.value); hoverDelay = parseFloat(hoverDelaySlider.value); wordunblurTransitionSpeed = parseFloat(transitionSlider.value); pageUnblurDelay = parseFloat(pageUnblurDelaySlider.value); pageUnblurSpeed = parseFloat(pageUnblurSpeedSlider.value); defaultFullPageBlur = defaultBlurCheckbox.checked; autoUnblurAfterProcessing = autoUnblurCheckbox.checked; isIPBlurEnabled = ipBlurCheckbox.checked; GM_setValue('blurWords', newWords); GM_setValue('exceptionList', newExceptions); GM_setValue('hoverDelay', hoverDelay); GM_setValue('pageBlurStrength', pageBlurStrength); GM_setValue('pageUnblurDelay', pageUnblurDelay); GM_setValue('pageUnblurSpeed', pageUnblurSpeed); GM_setValue('wordBlurStrength', wordBlurStrength); GM_setValue('wordunblurTransitionSpeed', wordunblurTransitionSpeed); GM_setValue('defaultFullPageBlur', defaultFullPageBlur); GM_setValue('autoUnblurAfterProcessing', autoUnblurAfterProcessing); GM_setValue('isIPBlurEnabled', isIPBlurEnabled); window.hidePanel(); location.reload(); } function handleKeyPress(e) { if (e.key === 'Escape') { if (panelElement && panelElement.style.display === 'block' && panelElement.classList.contains('visible')) { window.hidePanel(); } else if (isFullPageBlurred && !manualUnblurListenerActive) { removeFullPageBlur(); } } } toggleBtn.addEventListener('click', handleClick); panelElement.querySelector('#blur-close-button').addEventListener('click', window.hidePanel); panelElement.querySelector('#blur-cancel').addEventListener('click', window.hidePanel); panelElement.querySelector('#blur-save').addEventListener('click', saveSettings); document.addEventListener('keydown', handleKeyPress); } function findBlurPositions(text) { const positions = []; blurWords.forEach(word => { const escapedWord = word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const regex = new RegExp(escapedWord, 'gi'); let match; while ((match = regex.exec(text)) !== null) { positions.push({ start: match.index, end: match.index + match[0].length, word: match[0] }); } }); if (isIPBlurEnabled && userIP) { const ipRegex = new RegExp(userIP.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'); let match; while ((match = ipRegex.exec(text)) !== null) { positions.push({ start: match.index, end: match.index + match[0].length, word: match[0] }); } } positions.sort((a, b) => a.start - b.start); const mergedPositions = []; let current = null; for (const pos of positions) { if (!current) { current = {...pos}; } else if (pos.start <= current.end) { current.end = Math.max(current.end, pos.end); current.word = text.slice(current.start, current.end); } else { mergedPositions.push(current); current = {...pos}; } } if (current) { mergedPositions.push(current); } return mergedPositions; } function isProcessed(node) { if (!node) return true; if (!node.parentElement) return false; const isInput = node.parentElement.closest('input, textarea, [contenteditable="true"]'); if (isInput) return true; const isBlurSettings = node.parentElement.closest('#blur-settings-panel, #blur-ip-display'); if (isBlurSettings) return true; return processedNodes.has(node) || node.parentElement.classList.contains('word-blur') || node.parentElement.hasAttribute('data-blurred'); } function blurText(node) { if (!shouldBlurWordsOnPage || isProcessed(node)) return; const text = node.textContent; if (!text || !text.trim()) { processedNodes.add(node); return; } const positions = findBlurPositions(text); if (positions.length === 0) { processedNodes.add(node); return; } const container = document.createElement('span'); container.setAttribute('data-blurred', 'true'); let lastIndex = 0; positions.forEach(pos => { if (pos.start > lastIndex) { container.appendChild(document.createTextNode( text.slice(lastIndex, pos.start) )); } const blurSpan = document.createElement('span'); blurSpan.className = 'word-blur'; blurSpan.textContent = pos.word; container.appendChild(blurSpan); lastIndex = pos.end; }); if (lastIndex < text.length) { container.appendChild(document.createTextNode( text.slice(lastIndex) )); } try { if (node.parentNode) { node.parentNode.replaceChild(container, node); processedNodes.add(container); } else { processedNodes.add(node); } } catch (e) { processedNodes.add(node); } } function isElementInViewport(el) { if (!el || typeof el.getBoundingClientRect !== 'function') return false; if (!el.offsetParent && el.offsetWidth === 0 && el.offsetHeight === 0 && !el.closest('svg')) { return false; } const rect = el.getBoundingClientRect(); return ( rect.top < (window.innerHeight || document.documentElement.clientHeight) && rect.left < (window.innerWidth || document.documentElement.clientWidth) && rect.bottom > 0 && rect.right > 0 ); } function getBlurrableParent(node) { if (!node || !node.parentElement) return null; return node.parentElement.closest('p, div, span, li, td, th, h1, h2, h3, h4, h5, h6, a, pre, code, body') || node.parentElement; } function processBatch(nodes, startIndex, forRoot, isInitialVisibleScan) { if (processingActive && !isInitialVisibleScan && forRoot !== document.body) return; // Avoid parallel processing for sub-elements if main is running if (isInitialVisibleScan || forRoot === document.body && !initialVisibleScanComplete) { processingActive = true; } const nodesToProcessThisBatch = []; let currentIndex = startIndex; while (currentIndex < nodes.length && nodesToProcessThisBatch.length < BATCH_SIZE) { const node = nodes[currentIndex]; if (isInitialVisibleScan) { const blurrableParent = getBlurrableParent(node); if (blurrableParent && isElementInViewport(blurrableParent)) { nodesToProcessThisBatch.push(node); } else { deferredNodes.push(node); } } else { nodesToProcessThisBatch.push(node); } currentIndex++; } nodesToProcessThisBatch.forEach(node => { try { blurText(node); } catch (e) { } }); if (currentIndex < nodes.length) { requestAnimationFrame(() => processBatch(nodes, currentIndex, forRoot, isInitialVisibleScan)); } else { processingActive = false; if (forRoot === document.body) { if (isInitialVisibleScan && !initialVisibleScanComplete) { signalProcessingComplete(); } else if (!initialVisibleScanComplete) { signalProcessingComplete(); } } if (typeof forRoot.processingDoneCallback === 'function') { forRoot.processingDoneCallback(); delete forRoot.processingDoneCallback; } } } function processNodes(root, isInitialVisibleScan = false, customNodes = null) { if (!shouldBlurWordsOnPage) { if (root === document.body && isInitialVisibleScan && !initialVisibleScanComplete) { signalProcessingComplete(); } return; } let nodesToWalk = []; if (customNodes) { nodesToWalk = customNodes; } else { const walker = document.createTreeWalker( root, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { if (!node.textContent || node.textContent.trim() === '') return NodeFilter.FILTER_REJECT; if (node.parentElement?.nodeName === 'SCRIPT' || node.parentElement?.nodeName === 'STYLE' || node.parentElement?.nodeName === 'NOSCRIPT') { return NodeFilter.FILTER_REJECT; } if (node.parentElement?.closest('#blur-settings-panel, #blur-ip-display, #blur-toggle-button')) { return NodeFilter.FILTER_REJECT; } if (node.parentElement?.closest('input, textarea, [contenteditable="true"]')) { return NodeFilter.FILTER_REJECT; } if (isProcessed(node)) { return NodeFilter.FILTER_REJECT; } return NodeFilter.FILTER_ACCEPT; } } ); try { let currentNode; while (currentNode = walker.nextNode()) { nodesToWalk.push(currentNode); } } catch (e) { } } if (nodesToWalk.length > 0) { processBatch(nodesToWalk, 0, root, isInitialVisibleScan); } else { if (root === document.body) { if (isInitialVisibleScan && !initialVisibleScanComplete) { signalProcessingComplete(); } else if (!initialVisibleScanComplete) { signalProcessingComplete(); } } } } function checkForMissedNodes(root) { if (!initialVisibleScanComplete) return; if (processingActive && root !== document.body) return; const potentialDropdowns = root.querySelectorAll('div[style*="position"], div[style*="visibility"], div[style*="display"], div[class*="menu"], div[class*="dropdown"], ul, nav'); potentialDropdowns.forEach(element => { if (!element.closest('#blur-settings-panel')) { processNodes(element, false); } }); } const observer = new MutationObserver((mutations) => { if (!shouldBlurWordsOnPage) return; for (const mutation of mutations) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(node => { if (node.closest && node.closest('#blur-settings-panel')) return; let nodesToProcessFromMutation = []; if (node.nodeType === Node.ELEMENT_NODE) { nodesToProcessFromMutation.push(node); } else if (node.nodeType === Node.TEXT_NODE) { if (!isProcessed(node)) { if (!initialVisibleScanComplete) { deferredNodes.push(node); } else { blurText(node); } } return; } nodesToProcessFromMutation.forEach(n => { if (!initialVisibleScanComplete) { const walker = document.createTreeWalker(n, NodeFilter.SHOW_TEXT); let current; while(current = walker.nextNode()) { if (current.textContent.trim() && !isProcessed(current) && !current.parentElement?.closest('input, textarea, [contenteditable="true"], #blur-settings-panel')) { deferredNodes.push(current); } } } else { processNodes(n, false); requestAnimationFrame(() => { if (document.contains(n)) checkForMissedNodes(n); }); } }); }); } else if (mutation.type === 'characterData') { if (mutation.target.parentElement?.closest('#blur-settings-panel')) return; if (!isProcessed(mutation.target)) { if (!initialVisibleScanComplete) { deferredNodes.push(mutation.target); } else { blurText(mutation.target); } } } else if (mutation.type === 'attributes') { const target = mutation.target; if (target.nodeType === Node.ELEMENT_NODE && !target.closest('#blur-settings-panel') && (mutation.attributeName === 'style' || mutation.attributeName === 'class' || mutation.attributeName === 'aria-expanded')) { if (initialVisibleScanComplete) { requestAnimationFrame(() => { if (document.contains(target)) { processNodes(target, false); } }); } } } } }); function activateDelayedFunctionality() { if (document.body) { observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['style', 'class', 'aria-expanded', 'data-active', 'data-open', 'data-visible'] }); requestAnimationFrame(() => { const dropdowns = document.querySelectorAll('div[style*="position:absolute"], div[style*="visibility"], div[class*="menu"], div[class*="dropdown"], div[role="menu"], div[aria-expanded="true"]'); dropdowns.forEach(dropdown => { if (!dropdown.closest('#blur-settings-panel')) processNodes(dropdown, false); }); }); document.addEventListener('click', (e) => { if (!initialVisibleScanComplete) return; requestAnimationFrame(() => { const dropdowns = document.querySelectorAll('div[style*="position:absolute"], div[style*="visibility:visible"], div[role="menu"][aria-hidden="false"], div[role="menu"], div[aria-expanded="true"]'); dropdowns.forEach(dropdown => { if (!dropdown.closest('#blur-settings-panel')) processNodes(dropdown, false); }); }); }, true); document.addEventListener('mouseover', () => { if (!initialVisibleScanComplete) return; requestAnimationFrame(() => { const visibleDropdowns = document.querySelectorAll('div[style*="position:absolute"]:not([style*="display:none"]), div[style*="visibility:visible"]'); visibleDropdowns.forEach(dropdown => { if (!dropdown.closest('#blur-settings-panel')) processNodes(dropdown, false); }); }); }, true); } } function init() { determineBlurBehavior(); checkAndApplyInitialBlurState(); createSettings(); // Erstellt UI, fügt aber noch keine dynamischen Listener hinzu if (shouldBlurWordsOnPage) { if (document.body) { processNodes(document.body, true); // Startet initialen Scan für sichtbare Bereiche } else { const bodyObserver = new MutationObserver(() => { if (document.body) { processNodes(document.body, true); bodyObserver.disconnect(); } }); bodyObserver.observe(document.documentElement, { childList: true }); } } else { signalProcessingComplete(); // Nichts zu tun, also "fertig" } updateBlurStyles(); GM_registerMenuCommand('Blur-Einstellungen öffnen', () => { const panel = document.getElementById('blur-settings-panel'); if(panel && panel.style.display !== 'block') { if (window.showPanel) window.showPanel(); } else if (panel) { if (window.hidePanel) window.hidePanel(); } }); } if (document.readyState === 'loading' || document.readyState === 'interactive') { document.addEventListener('DOMContentLoaded', () => requestAnimationFrame(init)); } else { requestAnimationFrame(init); } })();