您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Massive integrated tool for ethical data research: forces media load; unblurs content (via CSS, canvas, OpenCV.js); reveals hidden text; recovers hidden data; fixes problematic overlays and unselectable text; extracts rich data (including high-detail image info), meta, and more; plus OCR, state-preserving UI, clearable logs, and live logging.
// ==UserScript== // @name Advanced Responsive Ethical Data Gathering Tool v3.2 // @namespace http://tampermonkey.net/ // @version 2025-02-11 // @description Massive integrated tool for ethical data research: forces media load; unblurs content (via CSS, canvas, OpenCV.js); reveals hidden text; recovers hidden data; fixes problematic overlays and unselectable text; extracts rich data (including high-detail image info), meta, and more; plus OCR, state-preserving UI, clearable logs, and live logging. // @author LittleLooney // @license Copyright (C) Littlelooney All rights reserved. // @match *://*/* // @icon https://www.google.com // @grant none // ==/UserScript== (function () { 'use strict'; /********************************** * Utility Functions & Logging * **********************************/ const DEBUG = true; // Debounce: Ensures that a function isn't called too frequently. function debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; } // Logging function: Logs messages with timestamps to console and to the sidebar log area. function logMessage(level, message) { const timestamp = new Date().toISOString(); const fullMessage = `[${timestamp}] [${level}] ${message}`; if (DEBUG) { console[level](fullMessage); } const logArea = document.getElementById('logArea'); if (logArea) { const logEntry = document.createElement('div'); logEntry.textContent = fullMessage; logEntry.style.borderBottom = "1px solid #444"; logEntry.style.padding = "2px 0"; logArea.appendChild(logEntry); // Auto-scroll to the bottom. logArea.scrollTop = logArea.scrollHeight; } } /********************************** * Force Media Loading Function * **********************************/ function forceLoadMedia() { try { // Process images. const images = document.querySelectorAll('img'); images.forEach(img => { if (img.hasAttribute('loading')) img.removeAttribute('loading'); // If there's a high-res version in a data attribute, use it. if (img.dataset && img.dataset.fullsrc) { img.src = img.dataset.fullsrc; } if (img.dataset && img.dataset.src) { img.src = img.dataset.src; } img.classList.remove('lazy', 'lazyload'); // Force reload if not complete. if (!img.complete || img.naturalWidth === 0) { img.src = img.src; } }); logMessage('info', `Force loaded ${images.length} image(s).`); // Process videos. const videos = document.querySelectorAll('video'); videos.forEach(video => { video.setAttribute('preload', 'auto'); video.load(); }); logMessage('info', `Force loaded ${videos.length} video(s).`); // Process iframes. const iframes = document.querySelectorAll('iframe'); iframes.forEach(iframe => { let src = iframe.getAttribute('src'); if (src) iframe.src = src; }); logMessage('info', `Force loaded ${iframes.length} iframe(s).`); } catch (error) { logMessage('error', `Error in forceLoadMedia: ${error}`); } } /********************************** * Unblurring Methods * **********************************/ // Remove CSS blur filters. function unblurElements() { try { const blurredElements = document.querySelectorAll('[style*="filter: blur"], .blurred'); blurredElements.forEach(el => { el.style.filter = 'none'; el.classList.remove('blurred'); }); logMessage('info', `Removed CSS blur filters from ${blurredElements.length} element(s).`); } catch (error) { logMessage('error', `Error in unblurElements: ${error}`); } } /********************************** * Canvas-Based Image Unblurring * **********************************/ function applyConvolution(imageData, kernel, kernelSize) { const width = imageData.width; const height = imageData.height; const inputData = imageData.data; const outputData = new Uint8ClampedArray(inputData.length); const half = Math.floor(kernelSize / 2); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { let r = 0, g = 0, b = 0, a = 0; for (let ky = -half; ky <= half; ky++) { for (let kx = -half; kx <= half; kx++) { const ix = x + kx; const iy = y + ky; if (ix >= 0 && ix < width && iy >= 0 && iy < height) { const idx = (iy * width + ix) * 4; const weight = kernel[(ky + half) * kernelSize + (kx + half)]; r += inputData[idx] * weight; g += inputData[idx + 1] * weight; b += inputData[idx + 2] * weight; a += inputData[idx + 3] * weight; } } } const outIdx = (y * width + x) * 4; outputData[outIdx] = Math.min(255, Math.max(0, r)); outputData[outIdx + 1] = Math.min(255, Math.max(0, g)); outputData[outIdx + 2] = Math.min(255, Math.max(0, b)); outputData[outIdx + 3] = Math.min(255, Math.max(0, a)); } } return new ImageData(outputData, width, height); } function sharpenImageData(imageData) { // Use a basic 3x3 sharpening kernel. const kernel = [ -1, -1, -1, -1, 9, -1, -1, -1, -1 ]; return applyConvolution(imageData, kernel, 3); } // Canvas-based unblur for images. function advancedUnblurImages_Canvas() { try { const imgs = document.querySelectorAll('img'); imgs.forEach(img => { try { img.style.filter = 'none'; // Remove any CSS blur. if (!img.complete || img.naturalWidth === 0) { logMessage('warn', 'Skipping an image because it is not fully loaded.'); return; } const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; context.drawImage(img, 0, 0); let imageData = context.getImageData(0, 0, canvas.width, canvas.height); let sharpenedData = sharpenImageData(imageData); context.putImageData(sharpenedData, 0, 0); img.src = canvas.toDataURL(); logMessage('info', 'Canvas-based advanced unblurring applied to an image.'); } catch (innerError) { logMessage('error', 'Error in canvas-based unblur: ' + innerError); } }); } catch (error) { logMessage('error', 'Error in advancedUnblurImages_Canvas: ' + error); } } /********************************** * OpenCV.js-Based Image Unblurring * **********************************/ function loadOpenCVJS(callback) { if (window.cv) { callback(); return; } const script = document.createElement('script'); script.src = "https://docs.opencv.org/4.x/opencv.js"; script.async = true; script.onload = function () { cv['onRuntimeInitialized'] = function() { logMessage('info', 'OpenCV.js runtime initialized.'); callback(); }; }; script.onerror = function() { logMessage('error', 'Failed to load OpenCV.js.'); }; document.body.appendChild(script); } function advancedUnblurImages_OpenCV() { loadOpenCVJS(() => { const imgs = document.querySelectorAll('img'); imgs.forEach(img => { try { img.style.filter = 'none'; if (!img.complete || img.naturalWidth === 0) { logMessage('warn', 'Skipping an image (OpenCV) because it is not fully loaded.'); return; } const canvas = document.createElement('canvas'); canvas.width = img.naturalWidth; canvas.height = img.naturalHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); let src = cv.imread(canvas); let dst = new cv.Mat(); let kernel = cv.matFromArray(3, 3, cv.CV_32F, [-1, -1, -1, -1, 9, -1, -1, -1, -1]); cv.filter2D(src, dst, cv.CV_8U, kernel); cv.imshow(canvas, dst); src.delete(); dst.delete(); kernel.delete(); img.src = canvas.toDataURL(); logMessage('info', 'OpenCV-based advanced unblurring applied to an image.'); } catch (e) { logMessage('error', 'Error in advancedUnblurImages_OpenCV: ' + e); } }); }); } // Combined unblur function. function advancedUnblurContent() { try { unblurElements(); forceLoadMedia(); advancedUnblurImages_Canvas(); // Optionally, also run advancedUnblurImages_OpenCV(); logMessage('info', 'Advanced unblur content executed.'); } catch (error) { logMessage('error', 'Error in advancedUnblurContent: ' + error); } } /********************************** * Reveal Hidden Text Function * **********************************/ /** * Scans elements (e.g., within .text_layer) and if the computed text color is transparent * or a heavy text-shadow is applied, sets the text color to black, removes/reduces the shadow, * and ensures full opacity. */ function revealHiddenText() { const textElements = document.querySelectorAll('.text_layer *'); textElements.forEach(el => { try { const cs = window.getComputedStyle(el); if (cs.color === "rgba(0, 0, 0, 0)" || cs.color === "transparent") { el.style.color = "black"; } if (cs.textShadow && cs.textShadow !== "none") { // Option: reduce the shadow blur (e.g., to 5px) instead of removing entirely. el.style.textShadow = "none"; } if (cs.opacity < 1) { el.style.opacity = "1"; } } catch (e) { logMessage('error', 'Error in revealHiddenText on an element: ' + e); } }); logMessage('info', 'Completed revealHiddenText() processing.'); } /********************************** * Advanced Hidden Data Recovery * **********************************/ function advancedRecoverHiddenContent() { let recoveredCount = 0; // Process media elements. const mediaSelectors = ['img', 'video', 'iframe', 'embed', 'object']; const mediaElements = document.querySelectorAll(mediaSelectors.join(',')); mediaElements.forEach(el => { try { const cs = window.getComputedStyle(el); const zIndex = parseInt(cs.zIndex) || 0; if (zIndex > 1000) return; let isHidden = (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0'); const rect = el.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) isHidden = true; if (isHidden) { el.style.display = 'block'; el.style.visibility = 'visible'; el.style.opacity = '1'; if (el.tagName.toLowerCase() === 'img' && (!el.complete || el.naturalWidth === 0)) { el.src = el.src; } recoveredCount++; } } catch (e) { logMessage('error', 'Error processing media element in advancedRecoverHiddenContent: ' + e); } }); // Process text elements. const textSelectors = ['p', 'span', 'div']; const textElements = document.querySelectorAll(textSelectors.join(',')); textElements.forEach(el => { try { const cs = window.getComputedStyle(el); const zIndex = parseInt(cs.zIndex) || 0; if (zIndex > 1000) return; let isHidden = (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0'); const rect = el.getBoundingClientRect(); if (rect.width < 5 || rect.height < 5) isHidden = true; const text = el.innerText.trim(); if (text.length > 5 && isHidden) { el.style.display = 'block'; el.style.visibility = 'visible'; el.style.opacity = '1'; recoveredCount++; } } catch (e) { logMessage('error', 'Error processing text element in advancedRecoverHiddenContent: ' + e); } }); logMessage('info', `Advanced recovered ${recoveredCount} content element(s).`); } /********************************** * Fix Elements for Accessibility * **********************************/ /** * 1. Removes any unselectable="on" attributes so that text can be selected. * 2. Hides known overlay elements (for example, with class "promo_div") * so that the blank/blurred box does not obscure the page. */ function fixElementsForAccessibility() { try { // Remove unselectable attributes. const unselectableElements = document.querySelectorAll('[unselectable="on"]'); unselectableElements.forEach(el => { el.removeAttribute('unselectable'); }); logMessage('info', `Removed unselectable attribute from ${unselectableElements.length} element(s).`); // Hide problematic overlays. const promoElements = document.querySelectorAll('.promo_div'); promoElements.forEach(el => { el.style.display = 'none'; }); logMessage('info', `Hid ${promoElements.length} promo element(s).`); } catch (error) { logMessage('error', 'Error in fixElementsForAccessibility: ' + error); } } /********************************** * Data Extraction Functions * **********************************/ function extractData() { let data = { texts: [], images: [], videos: [], docs: [] }; try { document.querySelectorAll('p, h1, h2, h3, h4, h5, h6').forEach(el => { const txt = el.innerText.trim(); if (txt) data.texts.push(txt); }); // For images, return an object with more details. data.images = Array.from(document.querySelectorAll('img')).map(img => ({ src: (img.dataset && img.dataset.fullsrc) ? img.dataset.fullsrc : img.src, naturalWidth: img.naturalWidth, naturalHeight: img.naturalHeight, displayedWidth: img.width, displayedHeight: img.height, alt: img.alt || "" })).filter(obj => obj.src); data.videos = Array.from(document.querySelectorAll('video, iframe')) .map(el => (el.tagName.toLowerCase() === 'video' ? (el.currentSrc || el.src) : el.src)) .filter(src => src); data.docs = Array.from(document.querySelectorAll('a')) .map(a => a.href) .filter(href => /\.(pdf|docx?|xlsx?|pptx?)($|\?)/i.test(href)); logMessage('info', 'Basic data extraction complete.'); } catch (error) { logMessage('error', `Error in extractData: ${error}`); } return data; } function extractMetaTags() { const metaData = {}; try { const metaTags = document.querySelectorAll( 'meta[property^="og:"], meta[name="description"], meta[name="keywords"], meta[name^="twitter:"]' ); metaTags.forEach(meta => { const key = meta.getAttribute('property') || meta.getAttribute('name'); const content = meta.getAttribute('content'); if (key && content) metaData[key] = content; }); logMessage('info', `Extracted ${Object.keys(metaData).length} meta tag(s).`); } catch (error) { logMessage('error', `Error in extractMetaTags: ${error}`); } return metaData; } function extractBackgroundImages() { const backgroundImages = []; try { const elements = document.querySelectorAll('[style*="background-image"]'); elements.forEach(el => { const style = el.getAttribute('style'); const regex = /background-image:\s*url\((['"]?)(.*?)\1\)/i; const match = regex.exec(style); if (match && match[2]) backgroundImages.push(match[2]); }); logMessage('info', `Extracted ${backgroundImages.length} background image(s).`); } catch (error) { logMessage('error', `Error in extractBackgroundImages: ${error}`); } return backgroundImages; } function extractHiddenForms() { const formsData = []; try { const hiddenInputs = document.querySelectorAll('form input[type="hidden"]'); hiddenInputs.forEach(input => { formsData.push({ name: input.name, value: input.value, form: input.form ? (input.form.action || 'N/A') : 'N/A' }); }); logMessage('info', `Extracted ${formsData.length} hidden form field(s).`); } catch (error) { logMessage('error', `Error in extractHiddenForms: ${error}`); } return formsData; } function extractShadowDOMData() { const shadowData = []; try { function traverseShadow(root) { let collectedText = ''; root.childNodes.forEach(node => { if (node.nodeType === Node.TEXT_NODE) { collectedText += node.textContent.trim() + ' '; } else if (node.nodeType === Node.ELEMENT_NODE) { collectedText += node.innerText.trim() + ' '; if (node.shadowRoot) { collectedText += traverseShadow(node.shadowRoot); } } }); return collectedText; } const allElements = document.querySelectorAll('*'); allElements.forEach(el => { if (el.shadowRoot) { const text = traverseShadow(el.shadowRoot); shadowData.push({ host: el.tagName, text: text.trim() }); } }); logMessage('info', `Extracted shadow DOM data from ${shadowData.length} element(s).`); } catch (error) { logMessage('error', `Error in extractShadowDOMData: ${error}`); } return shadowData; } function advancedExtractData() { const basicData = extractData(); const meta = extractMetaTags(); const backgrounds = extractBackgroundImages(); const hiddenForms = extractHiddenForms(); const shadow = extractShadowDOMData(); return { ...basicData, meta, backgrounds, hiddenForms, shadow }; } /********************************** * OCR Functionality (Tesseract) * **********************************/ function loadTesseractJS(callback) { if (window.Tesseract) { callback(); return; } const script = document.createElement('script'); script.src = "https://cdn.jsdelivr.net/npm/[email protected]/dist/tesseract.min.js"; script.onload = () => { logMessage('info', 'Tesseract.js loaded.'); callback(); }; script.onerror = () => { logMessage('error', 'Failed to load Tesseract.js.'); }; document.body.appendChild(script); } function performOCR() { try { const outputDiv = document.getElementById('dataOutput'); outputDiv.innerHTML += `<p>Starting OCR processing...</p>`; loadTesseractJS(() => { const imgs = Array.from(document.querySelectorAll('img')).filter(img => img.src); if (imgs.length === 0) { outputDiv.innerHTML += `<p>No images found for OCR.</p>`; logMessage('warn', 'No images available for OCR.'); return; } let ocrResults = []; let current = 0; function processNext() { if (current >= imgs.length) { outputDiv.innerHTML += `<p><strong>OCR Completed.</strong></p>`; outputDiv.innerHTML += `<details open> <summary>OCR Results (first 3)</summary> <pre style="white-space: pre-wrap;">${ocrResults.slice(0, 3).join('\n\n')}</pre> </details>`; logMessage('info', 'OCR processing completed.'); return; } const img = imgs[current]; outputDiv.innerHTML += `<p>Processing image ${current + 1} of ${imgs.length}...</p>`; window.Tesseract.recognize(img.src, 'eng', { logger: m => console.log(m) }) .then(result => { ocrResults.push(result.data.text.trim()); }) .catch(err => { ocrResults.push(`Error processing image: ${err}`); logMessage('error', `OCR error on image ${current + 1}: ${err}`); }) .finally(() => { current++; processNext(); }); } processNext(); }); } catch (error) { logMessage('error', `Error in performOCR: ${error}`); } } /********************************** * Sidebar Interface & Controls * **********************************/ function createSidebar() { let sidebar = document.getElementById('ethicalDataSidebar'); if (sidebar) return sidebar; sidebar = document.createElement('div'); sidebar.id = 'ethicalDataSidebar'; Object.assign(sidebar.style, { position: 'fixed', top: '0', right: '0', width: '400px', height: '100vh', backgroundColor: 'rgba(0,0,0,0.9)', color: '#fff', overflowY: 'auto', zIndex: '9999', padding: '10px', fontFamily: 'Arial, sans-serif', fontSize: '14px', lineHeight: '1.4' }); sidebar.innerHTML = ` <h2 style="margin-top:0;">Data Extraction</h2> <button id="refreshDataBtn" style="margin:5px 0;">Refresh Data</button> <button id="forceLoadMediaBtn" style="margin:5px 0;">Force Load Media</button> <button id="unblurContentBtn" style="margin:5px 0;">Advanced Unblur (Canvas)</button> <button id="unblurContentOpenCVBtn" style="margin:5px 0;">Advanced Unblur (OpenCV)</button> <button id="revealTextBtn" style="margin:5px 0;">Reveal Hidden Text</button> <button id="recoverHiddenBtn" style="margin:5px 0;">Recover Hidden Data</button> <button id="fixElementsBtn" style="margin:5px 0;">Enable Text Selection & Remove Overlays</button> <button id="ocrImagesBtn" style="margin:5px 0;">Perform OCR on Images</button> <button id="exportDataBtn" style="margin:5px 0;">Export JSON</button> <button id="clearLogsBtn" style="margin:5px 0;">Clear Logs</button> <div id="dataOutput" style="margin-top:10px;"></div> <hr> <h3>Logs</h3> <div id="logArea" style="max-height:150px; overflow-y:auto; background:#222; padding:5px; font-size:12px;"></div> `; document.body.appendChild(sidebar); document.getElementById('refreshDataBtn').addEventListener('click', updateSidebarData); document.getElementById('forceLoadMediaBtn').addEventListener('click', () => { forceLoadMedia(); updateSidebarData(); }); document.getElementById('unblurContentBtn').addEventListener('click', advancedUnblurContent); document.getElementById('unblurContentOpenCVBtn').addEventListener('click', advancedUnblurImages_OpenCV); document.getElementById('revealTextBtn').addEventListener('click', () => { revealHiddenText(); updateSidebarData(); }); document.getElementById('recoverHiddenBtn').addEventListener('click', () => { advancedRecoverHiddenContent(); updateSidebarData(); }); document.getElementById('fixElementsBtn').addEventListener('click', () => { fixElementsForAccessibility(); updateSidebarData(); }); document.getElementById('ocrImagesBtn').addEventListener('click', performOCR); document.getElementById('exportDataBtn').addEventListener('click', exportData); document.getElementById('clearLogsBtn').addEventListener('click', () => { const logArea = document.getElementById('logArea'); if (logArea) { logArea.innerHTML = ""; } }); logMessage('info', 'Sidebar created and event listeners attached.'); return sidebar; } // Update the sidebar with extracted data while preserving open <details> states. function updateSidebarData() { try { // Preserve current open state of details elements. const detailsStates = {}; document.querySelectorAll('#dataOutput details').forEach(d => { const summaryText = d.querySelector('summary')?.innerText || ""; detailsStates[summaryText] = d.hasAttribute("open"); }); const data = advancedExtractData(); const outputDiv = document.getElementById('dataOutput'); if (!outputDiv) return; // Update innerHTML with advanced image data. outputDiv.innerHTML = ` <p><strong>Text Blocks:</strong> ${data.texts.length}</p> <p><strong>Images:</strong> ${data.images.length}</p> <p><strong>Videos:</strong> ${data.videos.length}</p> <p><strong>Document Links:</strong> ${data.docs.length}</p> <hr> <details> <summary>View Text (first 5)</summary> <pre style="white-space: pre-wrap;">${data.texts.slice(0, 5).join('\n\n')}</pre> </details> <details> <summary>View Image Data (first 5)</summary> <pre style="white-space: pre-wrap;">${data.images.slice(0, 5).map(img => `src: ${img.src}\nnatural: ${img.naturalWidth}x${img.naturalHeight}\ndisplayed: ${img.displayedWidth}x${img.displayedHeight}\nalt: ${img.alt}` ).join('\n\n')}</pre> </details> <details> <summary>View Video URLs (first 5)</summary> <pre style="white-space: pre-wrap;">${data.videos.slice(0, 5).join('\n')}</pre> </details> <details> <summary>View Document Links (first 5)</summary> <pre style="white-space: pre-wrap;">${data.docs.slice(0, 5).join('\n')}</pre> </details> <hr> <details> <summary>Meta Tags (${Object.keys(data.meta).length})</summary> <pre style="white-space: pre-wrap;">${JSON.stringify(data.meta, null, 2)}</pre> </details> <details> <summary>Background Images (${data.backgrounds.length})</summary> <pre style="white-space: pre-wrap;">${data.backgrounds.slice(0, 5).join('\n')}</pre> </details> <details> <summary>Hidden Form Fields (${data.hiddenForms.length})</summary> <pre style="white-space: pre-wrap;">${JSON.stringify(data.hiddenForms.slice(0, 5), null, 2)}</pre> </details> <details> <summary>Shadow DOM Data (${data.shadow.length})</summary> <pre style="white-space: pre-wrap;">${data.shadow.slice(0, 3).map(item => item.host + ': ' + item.text).join('\n\n')}</pre> </details> `; // Reapply previous details open states. document.querySelectorAll('#dataOutput details').forEach(d => { const summaryText = d.querySelector('summary')?.innerText || ""; if (detailsStates[summaryText]) { d.setAttribute("open", ""); } }); logMessage('info', 'Sidebar data updated.'); } catch (error) { logMessage('error', `Error in updateSidebarData: ${error}`); } } function exportData() { try { const data = advancedExtractData(); const dataStr = JSON.stringify(data, null, 2); const blob = new Blob([dataStr], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = "extractedData.json"; a.click(); URL.revokeObjectURL(url); logMessage('info', 'Data exported as JSON.'); } catch (error) { logMessage('error', `Error in exportData: ${error}`); } } /********************************** * Other Core Functionalities * **********************************/ // Re-enable right-click and copy/paste. function enableRightClickAndCopyPaste() { try { const events = ['contextmenu', 'copy', 'cut', 'paste']; events.forEach(eventName => { document.addEventListener(eventName, function(e) { try { e.stopPropagation(); } catch (err) { logMessage('error', `Error in ${eventName} event: ${err}`); } }, true); }); logMessage('info', 'Right-click and copy-paste events re-enabled.'); } catch (error) { logMessage('error', `Error in enableRightClickAndCopyPaste: ${error}`); } } // Auto-expand "show more" / "read more" sections. function expandHiddenSections() { try { const buttons = document.querySelectorAll('button, a'); let clickCount = 0; buttons.forEach(btn => { try { const txt = btn.textContent.toLowerCase(); if (txt.includes('show more') || txt.includes('read more')) { btn.click(); clickCount++; } } catch (err) { logMessage('error', `Error processing button: ${err}`); } }); logMessage('info', `Clicked ${clickCount} "show more/read more" button(s).`); } catch (error) { logMessage('error', `Error in expandHiddenSections: ${error}`); } } /********************************** * Mutation Observer (Debounced) * **********************************/ const debouncedUpdate = debounce(() => { try { unblurElements(); expandHiddenSections(); updateSidebarData(); } catch (error) { logMessage('error', `Error in debounced DOM update: ${error}`); } }, 500); function observeDomChanges() { try { const observer = new MutationObserver(debouncedUpdate); observer.observe(document.body, { childList: true, subtree: true }); logMessage('info', 'DOM observer initialized.'); } catch (error) { logMessage('error', `Error in observeDomChanges: ${error}`); } } /********************************** * Initialization * **********************************/ function init() { try { enableRightClickAndCopyPaste(); unblurElements(); expandHiddenSections(); createSidebar(); updateSidebarData(); observeDomChanges(); logMessage('info', 'Advanced Responsive Ethical Data Gathering Tool initialized.'); } catch (error) { logMessage('error', `Error during init: ${error}`); } } window.addEventListener('load', init); })();