您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically toggles native dark mode based on local sunset/sunrise times
// ==UserScript== // @name Danbooru Theme Toggle (SunCalc + IP Geolocation) // @namespace Danbooru // @version 1.7 // @description Automatically toggles native dark mode based on local sunset/sunrise times // @author Dramorian // @match https://danbooru.donmai.us/* // @match https://aibooru.online/* // @run-at document-end // @require https://cdn.jsdelivr.net/npm/[email protected]/suncalc.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/global.min.js // @license MIT // ==/UserScript== /** * Manages automatic theme toggling for Danbooru based on sunrise/sunset times. * @class */ class DanbooruThemeToggle { #config = { debug: true, cacheKey: 'danbooru_theme_location', cacheDuration: 12 * 60 * 60 * 1000, // 12 hours in milliseconds }; /** * Logs messages if debug mode is enabled. * @param {...any} args - Values to log. */ #log(...args) { if (this.#config.debug) console.log('[DanbooruThemeToggle]', ...args); } /** * Logs errors if debug mode is enabled. * @param {...any} args - Error details to log. */ #logError(...args) { if (this.#config.debug) console.error('[DanbooruThemeToggle]', ...args); } /** * Retrieves or fetches location data, with caching. * @returns {Promise<{latitude: number, longitude: number, timezone: string}>} */ async #getLocationData() { this.#log('Checking for cached location data...'); const cachedData = localStorage.getItem(this.#config.cacheKey); if (cachedData) { const { timestamp, location } = JSON.parse(cachedData); const cacheAge = Temporal.Now.instant().since( Temporal.Instant.fromEpochMilliseconds(timestamp) ).total({ unit: 'millisecond' }); if (cacheAge < this.#config.cacheDuration) { this.#log('Using cached location:', location); return location; } this.#log('Cache expired, fetching new location data'); } else { this.#log('No cached data found, fetching new location data'); } try { this.#log('Fetching location from ipapi.co...'); const response = await fetch('https://ipapi.co/json/', { signal: AbortSignal.timeout(5000), // ES15 AbortSignal timeout }); const location = await response.json(); this.#log('Received location data:', { lat: location.latitude, lng: location.longitude, timezone: location.timezone, }); localStorage.setItem( this.#config.cacheKey, JSON.stringify({ timestamp: Temporal.Now.instant().epochMilliseconds, location, }) ); return location; } catch (error) { this.#logError('Location fetch failed:', error); throw new Error('Failed to fetch location data', { cause: error }); } } /** * Updates the theme based on sunrise/sunset times. * @returns {Promise<void>} */ async #updateThemeBasedOnSun() { this.#log('Starting theme update...'); if (typeof Danbooru === 'undefined' || !Danbooru?.CurrentUser) { this.#logError('Danbooru API unavailable'); return; } try { const { latitude, longitude, timezone } = await this.#getLocationData(); const lat = Number.parseFloat(latitude); const lng = Number.parseFloat(longitude); // Calculate sun times using Temporal API const now = Temporal.Now.zonedDateTimeISO(timezone); const { sunrise, sunset } = SunCalc.getTimes( new Date(now.toInstant().epochMilliseconds), lat, lng ); this.#log('Sun times:', { sunrise: sunrise.toUTCString(), sunset: sunset.toUTCString(), }); // Format times using Temporal const formatter = (date) => Temporal.Instant.fromEpochMilliseconds(date.getTime()) .toZonedDateTimeISO(timezone) .toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hourCycle: 'h24', }); const currentTime = now.toLocaleString('en-US', { hour: '2-digit', minute: '2-digit', hourCycle: 'h24', }); const sunriseTime = formatter(sunrise); const sunsetTime = formatter(sunset); this.#log('Formatted times:', { currentTime, sunriseTime, sunsetTime }); // Determine theme const isNightTime = currentTime >= sunsetTime || currentTime < sunriseTime; const currentThemeIsDark = Danbooru.CurrentUser.darkMode(); if (isNightTime !== currentThemeIsDark) { const newTheme = isNightTime ? 'dark' : 'light'; this.#log(`Updating to ${newTheme} mode...`); await Danbooru.CurrentUser.update({ theme: newTheme }); Danbooru.Utility.notice(`Theme updated to ${newTheme} mode.`); this.#log('Theme updated, reloading page'); window.location.reload(); } else { this.#log('Theme matches time of day, no update needed'); } } catch (error) { this.#logError('Theme update failed:', error); } } /** * Initializes the theme toggle script. * @returns {Promise<void>} */ async init() { this.#log('Script initialized'); await this.#updateThemeBasedOnSun(); } } // Start the script new DanbooruThemeToggle().init().catch((error) => console.error('[DanbooruThemeToggle] Initialization failed:', error) );