您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically and resiliently enables dark mode on Google, handling multiple UI versions and DOM changes.
// ==UserScript== // @name Force Google Dark Mode // @namespace http://tampermonkey.net/ // @version 1.0 // @description Automatically and resiliently enables dark mode on Google, handling multiple UI versions and DOM changes. // @author HappySmacky3453 // @license MIT // @match https://www.google.*/ // @match https://www.google.*/#* // @match https://www.google.*/search* // @match https://www.google.*/preferences* // @grant none // @run-at document-start // ==/UserScript== (function () { 'use strict'; // --- Configuration --- const LOG_PREFIX = '🌙 [Force Google Dark Mode]:'; const TIMEOUT_MS = 10000; const CLICK_DELAY_MS = 50; // A small delay to ensure element listeners are attached. // --- Selectors & Heuristics --- const SELECTORS = { MENU_ITEM_REGEX: /dark theme: off|light theme|appearance: (device|light|default)/i, }; const DARK_MODE_COLOR_HEURISTIC = 'rgb(32, 33, 36)'; // --- State --- let observer; let timeoutId; /** * Checks if dark mode is already enabled using multiple reliable methods. * @returns {boolean} */ const isDarkModeAlreadyOn = () => { const html = document.documentElement; if (html.hasAttribute('dark')) return true; if (html.getAttribute('data-darkreader-scheme') === 'dark') return true; const metaTheme = document.querySelector('meta[name="color-scheme"]'); if (metaTheme?.content.includes('dark')) return true; if (document.body && getComputedStyle(document.body).backgroundColor === DARK_MODE_COLOR_HEURISTIC) return true; return false; }; /** * Attempts to find and click a dark mode toggle element. * @returns {boolean} True if a toggle was found. */ const tryToEnableDarkMode = () => { if (isDarkModeAlreadyOn()) { console.log(LOG_PREFIX, '✅ Dark mode is already active. Cleaning up.'); cleanUp(); return true; } let elementToClick = null; // Strategy 1: Find the main menu item toggle. const menuToggle = [...document.querySelectorAll('[role="menuitem"]')] .find(el => SELECTORS.MENU_ITEM_REGEX.test(el.textContent)); if (menuToggle) { elementToClick = menuToggle; } // Strategy 2: Find the radio button on the preferences page. if (!elementToClick) { const radioToggle = [...document.querySelectorAll('g-radio-button')] .find(btn => btn.closest('[role="radiogroup"]') && /dark/i.test(btn.textContent || btn.getAttribute('aria-label') || '') ); if (radioToggle && radioToggle.getAttribute('aria-checked') !== 'true') { elementToClick = radioToggle; } } if (elementToClick) { console.log(LOG_PREFIX, 'Found toggle element. Attempting to click...', elementToClick); try { // Use a short delay for stability on dynamic pages. setTimeout(() => { elementToClick.click(); console.log(LOG_PREFIX, '✅ Successfully triggered dark mode toggle.'); }, CLICK_DELAY_MS); // Once we find and trigger the element, we don't need to search again on this pass. // The next mutation will confirm success via isDarkModeAlreadyOn(). return true; } catch (err) { console.error(LOG_PREFIX, '⚠️ Failed to click dark mode toggle:', err); } } return false; }; /** * Stops observing DOM changes and clears the safety timeout. */ const cleanUp = () => { if (observer) { observer.disconnect(); observer = null; // Allow garbage collection console.log(LOG_PREFIX, 'Observer disconnected.'); } if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } }; // --- Execution --- if (window.top !== window.self) return; // Don't run on iframes console.log(LOG_PREFIX, 'Script starting.'); // Initial attempt. If it succeeds immediately, the script will clean up and exit. if (tryToEnableDarkMode()) { // The check inside tryToEnableDarkMode() will call cleanUp() if already on. return; } // If not immediately found/active, observe for changes. observer = new MutationObserver(tryToEnableDarkMode); // Pass function reference directly observer.observe(document.documentElement, { childList: true, subtree: true, }); // Set a timeout to prevent the script from running forever. timeoutId = setTimeout(() => { if (observer) { // Only log timeout if observer is still active console.log(LOG_PREFIX, `⏱️ Timed out after ${TIMEOUT_MS / 1000}s. Toggle not found.`); cleanUp(); } }, TIMEOUT_MS); })();