您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Replaces low-resolution favicons in Google search results with higher-quality versions.
// ==UserScript== // @name Google Search Better Favicons // @namespace https://greasyfork.org/en/users/1467948-stonedkhajiit // @version 0.1.0 // @description Replaces low-resolution favicons in Google search results with higher-quality versions. // @author StonedKhajiit // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com // @match https://www.google.tld/search* // @exclude /^https?:\/\/([a-z0-9-]+\.)*google\.[a-z.]+\/search.*(udm=2|udm=7|udm=28|udm=36|udm=39)/ // @run-at document-end // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== (function() { 'use strict'; // ---[ 1. CONFIGURATION & STATE ]--- const GSBF_FAVICON_SHAPE_KEY = 'gsbf_favicon_shape'; const GSBF_SQUARE_FILL_KEY = 'gsbf_square_fill'; const GSBF_MENU_COMMAND_SHAPE_ID = 'gsbf_toggle_favicon_shape'; const GSBF_MENU_COMMAND_FILL_ID = 'gsbf_toggle_square_fill'; const SELECTORS = { // Selector for result items on the "News" tab, used for style exclusions. newsSearchResult: '.SoaBEf', // The primary selector for containers that are treated as a single search result. searchResultItem: '.MjjYud, .SoaBEf, .m7jPZ, .A6K0A', // Selects the element that directly contains the favicon image within a result. faviconContainer: 'span.DDKf1c, span.H9lube, g-img.QyR1Ze', // Selects the main link within various result item structures. linkElement: '.yuRUbf a[href], .xe8e1b a[href], a.WlydOe[href], .a-no-hover-decoration[href], .h4kbcd[href]' }; // ---[ 2. STYLES ]--- const STYLES = ` /* gsbf-processed-favicon: Base class for all script-handled icons. */ /* gsbf-upgraded-favicon: Added only after a successful HQ replacement to trigger the fill effect. */ /* Style the image directly for .DDKf1c containers. */ body.gsbf-square-favicons :is(.MjjYud, .m7jPZ, .A6K0A):not(:has(.SoaBEf)) .DDKf1c.gsbf-processed-favicon img { border-radius: 4px !important; } /* Style the container for .H9lube containers to create a mask. */ body.gsbf-square-favicons :is(.MjjYud, .m7jPZ, .A6K0A):not(:has(.SoaBEf)) .H9lube.gsbf-processed-favicon { border-radius: 4px !important; overflow: hidden !important; } /* Ensure the image inside a styled .H9lube container remains a true square. */ body.gsbf-square-favicons :is(.MjjYud, .m7jPZ, .A6K0A):not(:has(.SoaBEf)) .H9lube.gsbf-processed-favicon img { border-radius: 0 !important; } /* Enlarge the icon to fill its container if it was successfully upgraded. */ body.gsbf-square-favicons.gsbf-square-fill-enabled :is(.MjjYud, .m7jPZ, .A6K0A):not(:has(.SoaBEf)) .H9lube.gsbf-upgraded-favicon img.XNo5Ab { width: 26px !important; height: 26px !important; } /* Add a transition effect for smoothness. */ .gsbf-processed-favicon, .gsbf-processed-favicon img { transition: border-radius 0.2s ease-in-out, width 0.2s ease-in-out, height 0.2s ease-in-out; } `; // ---[ 3. CORE LOGIC ]--- /** * Reads all stored preferences and applies the corresponding CSS classes to the document body. */ function applyPreferences() { // Apply shape preference, with "square" as the new default. const shape = GM_getValue(GSBF_FAVICON_SHAPE_KEY, 'square'); document.body.classList.toggle('gsbf-square-favicons', shape === 'square'); // Apply square fill preference const fill = GM_getValue(GSBF_SQUARE_FILL_KEY, true); // Default to true (enabled) document.body.classList.toggle('gsbf-square-fill-enabled', fill); updateMenuCommands(); } /** * Registers and updates the state of all toggleable menu commands. */ function updateMenuCommands() { // Command 1: Toggle favicon shape const currentShape = GM_getValue(GSBF_FAVICON_SHAPE_KEY, 'square'); const isSquare = currentShape === 'square'; const shapeMenuText = `${isSquare ? '[✓]' : '[ ]'} Use Square Favicons`; GM_registerMenuCommand(shapeMenuText, () => { GM_setValue(GSBF_FAVICON_SHAPE_KEY, isSquare ? 'circle' : 'square'); applyPreferences(); }, { id: GSBF_MENU_COMMAND_SHAPE_ID }); // Command 2: Toggle fill effect for square icons const currentFill = GM_getValue(GSBF_SQUARE_FILL_KEY, true); const fillMenuText = ` ${currentFill ? '[✓]' : '[ ]'} Enlarge HQ Square Icons to Fill`; GM_registerMenuCommand(fillMenuText, () => { GM_setValue(GSBF_SQUARE_FILL_KEY, !currentFill); applyPreferences(); }, { id: GSBF_MENU_COMMAND_FILL_ID }); } /** * Enhances a single favicon by replacing it with a high-quality version if available. * @param {HTMLElement} faviconContainer - The DOM element containing the favicon. * @param {string} domain - The domain of the search result. */ function enhanceResultFavicon(faviconContainer, domain) { if (!faviconContainer || !domain) return; // Immediately add the 'processed' class to apply basic styles. faviconContainer.classList.add('gsbf-processed-favicon'); const existingImg = faviconContainer.querySelector('img'); const existingSvg = faviconContainer.querySelector('svg'); // If Google shows a generic SVG placeholder, skip network request. if (existingSvg) { return; } // Proceed only if there is an actual image to potentially replace. if (!existingImg) { return; } const targetSize = 96; const highQualityUrl = `https://www.google.com/s2/favicons?sz=${targetSize}&domain=${domain}`; const newImg = new Image(); newImg.onload = () => { // Replace the icon only if the new one is higher resolution. if (newImg.naturalWidth > (existingImg.naturalWidth || 16)) { existingImg.src = highQualityUrl; // Add the 'upgraded' class only AFTER successful replacement. faviconContainer.classList.add('gsbf-upgraded-favicon'); } }; newImg.onerror = () => { /* Graceful failure */ }; newImg.src = highQualityUrl; } /** * Processes a single search result DOM node to find and enhance its favicon. * @param {HTMLElement} resultNode - The container element of a single search result. */ function processResultItem(resultNode) { if (resultNode.dataset.gsbfProcessed) return; resultNode.dataset.gsbfProcessed = 'true'; const faviconContainer = resultNode.querySelector(SELECTORS.faviconContainer); const linkElement = resultNode.querySelector(SELECTORS.linkElement); if (!faviconContainer || !linkElement) return; try { let targetUrl; const href = linkElement.href; // Handle Google's redirect links. if (href && href.startsWith(window.location.origin + '/url?q=')) { targetUrl = new URL(href).searchParams.get('q'); } else { targetUrl = href; } const domain = new URL(targetUrl).hostname; enhanceResultFavicon(faviconContainer, domain); } catch(e) { // Ignore nodes that don't have a valid URL structure. } } /** * Sets up the MutationObserver and initializes the script. */ function main() { GM_addStyle(STYLES); applyPreferences(); const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const addedNode of mutation.addedNodes) { if (addedNode.nodeType === 1) { if (addedNode.matches(SELECTORS.searchResultItem)) { processResultItem(addedNode); } addedNode.querySelectorAll(SELECTORS.searchResultItem).forEach(processResultItem); } } } }); observer.observe(document.body, { childList: true, subtree: true }); // Process results already on the page at script load. document.querySelectorAll(SELECTORS.searchResultItem).forEach(processResultItem); } main(); })();