您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Remove .row-header-wrapper elements on Sheets diffs, modify Google redirect links (including CSE & Search), remove srsltid parameter from links, and decode %3D to = in table text.
当前为
// ==UserScript== // @name Google Sheets Diff alerts mods & Google Search Link Cleaner // @namespace http://tampermonkey.net/ // @version 3.8 // @description Remove .row-header-wrapper elements on Sheets diffs, modify Google redirect links (including CSE & Search), remove srsltid parameter from links, and decode %3D to = in table text. // @author luascfl // @icon https://e7.pngegg.com/pngimages/660/350/png-clipart-green-and-white-sheet-icon-google-docs-google-sheets-spreadsheet-g-suite-google-angle-rectangle-thumbnail.png // @match https://docs.google.com/spreadsheets/d/*/notify/show* // @match https://docs.google.com/spreadsheets/u/*/d/*/revisions/show* // @match https://cse.google.com/* // @match https://www.google.com/search* // @home https://github.com/luascfl/gsheets-diff-alerts-mods // @supportURL https://github.com/luascfl/gsheets-diff-alerts-mods/issues // @license MIT // @grant none // @run-at document-start // ==/UserScript== (function() { 'use strict'; const INTERVALO = 100; // Interval in milliseconds // --- Helper Functions --- // Function to check if the current page is a Google Sheets diff/revision page function isSheetsPage() { const href = window.location.href; return href.includes('/spreadsheets/d/') && (href.includes('/notify/show') || href.includes('/revisions/show')); } // Function to remove row headers (only on Sheets pages) function removerElementos() { if (isSheetsPage()) { document.querySelectorAll('.row-header-wrapper').forEach(el => { el.remove(); }); } } // Function to modify links (redirects and srsltid) and decode text function modificarLinksETextos() { // Process all links on the page for redirects and srsltid document.querySelectorAll('a').forEach(link => { processarLink(link); }); // Only decode %3D within table bodies (relevant for Sheets/CSE) if (isSheetsPage() || window.location.href.includes('cse.google.com')) { document.querySelectorAll('tbody a').forEach(link => { decodificarEncodingNoLink(link); // Decode %3D in table links }); decodificarTextosEmTbody(); // Decode %3D in table text } } // Function to process each individual link - MODIFIED function processarLink(link) { let currentHref = link.getAttribute('href'); // Get current href attribute value let currentDataHref = link.getAttribute('data-href'); // Get current data-href attribute value let hrefChanged = false; let dataHrefChanged = false; // 1. Handle Google Redirects (google.com/url?q=...) if (currentHref && currentHref.includes('google.com/url?')) { try { const urlObj = new URL(currentHref); const params = urlObj.searchParams; if (params.has('q')) { currentHref = params.get('q'); // Update currentHref with the real URL hrefChanged = true; } } catch (e) { console.warn('Erro ao processar URL de redirecionamento (href):', link.href, e); } } // Also check data-href for redirects if (currentDataHref && currentDataHref.includes('google.com/url?')) { try { const dataUrlObj = new URL(currentDataHref); const dataParams = dataUrlObj.searchParams; if (dataParams.has('q')) { currentDataHref = dataParams.get('q'); // Update currentDataHref dataHrefChanged = true; } } catch (e) { console.warn('Erro ao processar URL de redirecionamento (data-href):', link.getAttribute('data-href'), e); } } // 2. Remove srsltid parameter from the potentially updated href if (currentHref && (currentHref.includes('?srsltid=') || currentHref.includes('&srsltid='))) { try { const urlObj = new URL(currentHref); if (urlObj.searchParams.has('srsltid')) { urlObj.searchParams.delete('srsltid'); currentHref = urlObj.toString(); // If the URL ends with '?' after removal, remove it too if (currentHref.endsWith('?')) { currentHref = currentHref.slice(0, -1); } hrefChanged = true; } } catch (e) { console.warn('Erro ao remover srsltid (href):', currentHref, e); // Attempt simple string replacement as fallback (less robust) const paramIndex = currentHref.indexOf('srsltid='); if (paramIndex > 0) { const charBefore = currentHref[paramIndex - 1]; if (charBefore === '?' || charBefore === '&') { // Find the end of the parameter (next '&' or end of string) const nextAmp = currentHref.indexOf('&', paramIndex); if (nextAmp !== -1) { currentHref = currentHref.substring(0, paramIndex -1) + currentHref.substring(nextAmp); // Remove '&srsltid=...' } else { currentHref = currentHref.substring(0, paramIndex -1); // Remove '?srsltid=...' or '&srsltid=...' at the end } hrefChanged = true; } } } } // Also remove srsltid from the potentially updated data-href if (currentDataHref && (currentDataHref.includes('?srsltid=') || currentDataHref.includes('&srsltid='))) { try { const dataUrlObj = new URL(currentDataHref); if (dataUrlObj.searchParams.has('srsltid')) { dataUrlObj.searchParams.delete('srsltid'); currentDataHref = dataUrlObj.toString(); // If the URL ends with '?' after removal, remove it too if (currentDataHref.endsWith('?')) { currentDataHref = currentDataHref.slice(0, -1); } dataHrefChanged = true; } } catch (e) { console.warn('Erro ao remover srsltid (data-href):', currentDataHref, e); // Add fallback string replacement for data-href if needed, similar to href } } // 3. Apply changes if any occurred if (hrefChanged) { link.href = currentHref; // Set the final href property } if (dataHrefChanged) { link.setAttribute('data-href', currentDataHref); // Set the final data-href attribute } } // Function specifically for decoding %3D to = in links within tbody function decodificarEncodingNoLink(link) { // Decode %3D to = in href (both property and attribute) let hrefChanged = false; let currentHref = link.getAttribute('href'); if (currentHref && currentHref.includes('%3D')) { currentHref = currentHref.replaceAll('%3D', '='); hrefChanged = true; } if (hrefChanged) { link.setAttribute('href', currentHref); // Also update the property in case the attribute update doesn't reflect immediately link.href = currentHref; } // Decode %3D to = in data-href if it exists if (link.hasAttribute('data-href')) { const dataHref = link.getAttribute('data-href'); if (dataHref.includes('%3D')) { link.setAttribute('data-href', dataHref.replaceAll('%3D', '=')); } } // Decode %3D to = in the link's text content if needed if (link.textContent.includes('%3D')) { link.textContent = link.textContent.replaceAll('%3D', '='); } // Check if visible text is correct but href is not (re-check after potential changes) if (link.textContent.includes('=') && !link.textContent.includes('%3D')) { let hrefAttr = link.getAttribute('href'); // Get potentially updated href if (hrefAttr && hrefAttr.includes('%3D')) { const paramsInText = link.textContent.match(/[?&][^?&=]+=[^?&=]+/g); if (paramsInText) { let hrefAtual = hrefAttr; paramsInText.forEach(param => { const [paramName, paramValue] = param.substring(1).split('='); // Look for the encoded version in the href const encodedParam = `${paramName}%3D${encodeURIComponent(paramValue)}`; // More robust encoding check might be needed const encodedParamSimple = `${paramName}%3D${paramValue}`; // Simpler check if (hrefAtual.includes(encodedParamSimple)) { hrefAtual = hrefAtual.replaceAll(encodedParamSimple, `${paramName}=${paramValue}`); } else if (hrefAtual.includes(encodedParam)) { hrefAtual = hrefAtual.replaceAll(encodedParam, `${paramName}=${paramValue}`); } }); if (hrefAtual !== hrefAttr) { link.setAttribute('href', hrefAtual); link.href = hrefAtual; // Update property too } } } } } // Function to decode %3D in all text elements within tbody function decodificarTextosEmTbody() { document.querySelectorAll('tbody').forEach(tbody => { iterarESusbstituirTextoEmElemento(tbody); }); } // Recursive function to iterate over all child nodes and replace text function iterarESusbstituirTextoEmElemento(elemento) { Array.from(elemento.childNodes).forEach(node => { if (node.nodeType === Node.TEXT_NODE) { if (node.textContent.includes('%3D')) { node.textContent = node.textContent.replaceAll('%3D', '='); } } else if (node.nodeType === Node.ELEMENT_NODE && node.nodeName !== 'A') { // Don't re-process links here // Check common attributes ['value', 'title', 'alt', 'placeholder', 'data-text'].forEach(attr => { let attrValue = null; if (attr === 'value' && node.value && typeof node.value === 'string') { attrValue = node.value; if (attrValue.includes('%3D')) { node.value = attrValue.replaceAll('%3D', '='); } } else if (node.hasAttribute(attr)) { attrValue = node.getAttribute(attr); if (attrValue.includes('%3D')) { node.setAttribute(attr, attrValue.replaceAll('%3D', '=')); } } }); // Recurse into children iterarESusbstituirTextoEmElemento(node); } }); } // Function that combines all functionalities function processarPagina() { removerElementos(); // Runs conditionally inside the function modificarLinksETextos(); // Processes links/text based on page type inside } // --- Execution Logic --- // Configuration of the observer to detect DOM changes let observer; // Use a simple debounce to avoid excessive processing with the observer let timeoutId; const debouncedProcessarPagina = () => { clearTimeout(timeoutId); timeoutId = setTimeout(processarPagina, 50); // 50ms delay for debounce }; const callback = (mutationsList, observerInstance) => { // Check if any relevant mutation occurred (node addition or attribute changes) // This avoids reprocessing the entire page for trivial changes let relevantChange = false; for (const mutation of mutationsList) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { // Check if added nodes contain links or if we are on sheets page where structure might change let addedLinks = false; mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { if (node.matches('a') || node.querySelector('a')) { addedLinks = true; } // On sheets page, any table change could be relevant if (isSheetsPage() && (node.matches('tbody, tr, td') || node.querySelector('tbody, tr, td'))) { addedLinks = true; // Treat table changes as relevant for Sheets } } }); if(addedLinks){ relevantChange = true; break; } } if (mutation.type === 'attributes' && (mutation.attributeName === 'href' || mutation.attributeName === 'data-href')) { relevantChange = true; break; } } if (relevantChange) { debouncedProcessarPagina(); } }; // Execute immediately and maintain interval (Interval may be less necessary with Observer) (function loop() { processarPagina(); // Execute once // The loop can be removed or have a longer interval if the Observer is reliable setTimeout(loop, INTERVALO * 10); // Increased interval, as the observer should catch most changes })(); // Ensure execution after initial full load window.addEventListener('load', () => { processarPagina(); // Start the observer after the initial load and first processing if (!observer) { observer = new MutationObserver(callback); // Use the callback defined above observer.observe(document.documentElement || document.body, { childList: true, subtree: true, attributes: true, // Observe attribute changes too (important for href/data-href) attributeFilter: ['href', 'data-href'] // Focus on relevant attributes }); } }); // DOMNodeInserted listener is legacy and can cause performance issues. // The MutationObserver above is the modern and more efficient way. // Removed the old listener. })();