// ==UserScript== // @name 夜间护眼助手 // @namespace Local // @version 5.21.11 // @description 全网通用夜间护眼助手,整合原版深色、柔和护眼、护眼暗色、豆沙绿、深暖色(源自绿黄黑护眼模式),优化代码页面显示,支持网站专用模式(白天/夜间/滤镜/原版深色/柔和护眼/护眼暗色/豆沙绿/深暖色)。全局循环模式可自定义,支持添加/删除/排序。菜单中专用模式加[专用]标记,专用模式设置支持输入完整网址或域名,模式说明可折叠。全局豆沙绿/深暖色浓度调节位于网站专用模式设置之后,仅在对应全局模式下显示。网站专用模式设置中增加“全局专用总开关”,与开关同行显示状态。智能排除仅排除明显深色背景的网站(RGB总和<150),白名单网站不受影响。全局设置开关实时生效。优化按钮样式和滑块显示。新增“自动关闭专用模式的全局模式”功能:选择某个模式后,切换到该模式时自动关闭专用总开关,离开时恢复之前状态。修复退出自动模式后恢复专用模式的问题。移除全局专用总开关的背景色。 // @match *://*/* // @run-at document-start // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNOTMuNSA5NC42YzEwLjYgMCAyMC4zLTMuMyAyOC4yLTktOC4zIDIyLjUtMzAuMiAzOC42LTU2IDM4LjYtMzIuNyAwLTU5LjMtMjUuOC01OS4zLTU3LjdTMzIuOSA4LjcgNjUuNyA4LjdoMi4yQzU0LjYgMTcgNDUuNyAzMS41IDQ1LjcgNDhjMCAyNS43IDIxLjQgNDYuNiA0Ny44IDQ2LjZ6IiBmaWxsPSIjZmZiNTc4Ii8+PHBvbHlnb24gcG9pbnRzPSI2NCw0MCA2OS4yOSw1Ni43MiA4Ni44Miw1Ni41OCA3Mi41Niw2Ni43OCA3OC4xMSw4My40MiA2NCw3MyA0OS44OSw4My40MiA1NS40NCw2Ni43OCA0MS4xOCw1Ni41OCA1OC43MSw1Ni43MiIgZmlsbD0iIzQ0NCIvPjwvc3ZnPg== // ==/UserScript== ;(function () { 'use strict'; // 存储所有菜单命令ID let menuCommands = []; let EyeProtect = { // 当前模式存储 currentMode: null, // 默认配置 defaults: { globalEnable: false, // 全局开关 enableList: [], // 启用列表(白名单) blacklist: [], // 禁用列表(黑名单) autoExclude: true, // 智能排除 forcedEnableList: [], // 强制启用列表 originThemeColor: '#ffffff', // 原始主题色 runDuringDay: true, // 白天保持开启 darkAuto: false, // 跟随浏览器暗色模式 customDayNight: '6:00|18:00', // 自定义昼夜时间 autoSwitch: '', // 自动切换模式 customDark1: '60|50', // 亮度模式设置 customDark2: '60|40|50|50', // 暖色模式设置 customDark3: '90', // 反色强度设置 dark3Exclude: 'img, .img, video, [style*="background"][style*="url"], svg, .video-player, .player, [class*="player"], [class*="Player"], [id*="player"], [id*="Player"], .plyr, .jw-player, .video-js', // 排除元素 filterExcludeList: ['youku.com', 'v.youku.com', 'www.douyu.com', 'www.iqiyi.com', 'vip.iqiyi.com', 'mail.qq.com', 'live.kuaishou.com'], // 滤镜模式排除列表 siteSpecificModes: '{}', // 网站专用模式(支持对象格式,如 {"example.com": {mode:"green", opacity:0.4}}) tempDisableAllSiteModes: false, // 全局暂时关闭所有专用模式 autoDisableTempMode: '', // 自动关闭专用模式的全局模式,如 'dark' // 全局循环模式列表(仅包含 dark/classic/soft/eyecare/filter/green/warm,顺序代表切换顺序) globalCycleModes: ['dark'], // 默认仅夜间模式 // 全局豆沙绿浓度(当全局模式为豆沙绿且无网站专用模式覆盖时使用) globalGreenOpacity: 0.3, // 全局深暖色浓度(当全局模式为深暖色且无网站专用模式覆盖时使用) globalWarmOpacity: 0.3 }, // 绿色蒙层元素 greenOverlay: null, // 深暖色蒙层元素 warmOverlay: null, // 记录进入自动关闭模式前的专用总开关状态,用于离开时恢复 _prevTempDisableState: null, // 初始化 init() { this.initConfig(); // 获取当前模式,修复可能的模式错误(允许light/dark/classic/soft/eyecare/filter/green/warm) this.fixModeIssue(); // 立即应用基础样式(不等待DOM加载) this.applyImmediateMode(); // 延迟保存原始主题色 setTimeout(() => this.saveOriginThemeColor(), 1000); this.initMenu(); // 监听系统主题变化 if (window.matchMedia) { window.matchMedia('(prefers-color-scheme: dark)').addListener(() => { this.applyMode(); this.refreshMenu(); }); } // 监听全屏变化(用于滤镜模式) document.addEventListener("fullscreenchange", () => this.handleFullscreenChange()); document.addEventListener("webkitfullscreenchange", () => this.handleFullscreenChange()); document.addEventListener("mozfullscreenchange", () => this.handleFullscreenChange()); // 等待DOM加载完成后应用完整模式 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { this.applyMode(); // 监听页面动态加载 this.observeDOMChanges(); }, 100); }); } else { setTimeout(() => { this.applyMode(); this.observeDOMChanges(); }, 100); } }, // 处理全屏变化 handleFullscreenChange() { if (this.isFullscreen()) { // 进入全屏时禁用滤镜模式 if (this.currentMode === 'filter') { this.disableFilterMode(); } } else { // 退出全屏时恢复滤镜模式 if (this.currentMode === 'filter' && this.shouldApplyMode()) { setTimeout(() => this.applyFilterMode(), 100); } } }, // 判断是否全屏 isFullscreen() { return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement; }, // 立即应用基础模式(在DOM加载前执行) applyImmediateMode() { // 检查是否应该应用模式 if (this.shouldApplyMode()) { // 创建通用的立即样式 let style = document.createElement('style'); style.id = 'eye-protect-immediate'; style.innerHTML = ` /* 立即模式 - 防止页面闪烁 */ html, body { transition: none !important; } `; // 立即插入到head中 if (document.head) { document.head.appendChild(style); } else { // 如果head不存在,直接插入到document document.documentElement.appendChild(style); } } }, // 修复模式问题:确保模式正确(允许light/dark/classic/soft/eyecare/filter/green/warm) fixModeIssue() { let storedMode = this.getConfig('currentMode'); // 全局模式有效值 const validGlobalModes = ['light', 'dark', 'classic', 'soft', 'eyecare', 'filter', 'green', 'warm']; // 如果存储的模式不是有效模式,重置为light if (!validGlobalModes.includes(storedMode)) { this.setConfig('currentMode', 'light'); this.currentMode = 'light'; } else { this.currentMode = storedMode; } }, // 初始化配置 initConfig() { for (let key in this.defaults) { let value = GM_getValue(key); if (value === undefined) { GM_setValue(key, this.defaults[key]); } } // 初始化当前模式 if (GM_getValue('currentMode') === undefined) { GM_setValue('currentMode', 'light'); this.currentMode = 'light'; } }, // 获取配置值 getConfig(key) { return GM_getValue(key); }, // 设置配置值 setConfig(key, value) { GM_setValue(key, value); }, // 保存原始主题色 saveOriginThemeColor() { let meta = document.querySelector('meta[name="theme-color"]'); if (meta && meta.content) { this.setConfig('originThemeColor', meta.content); } }, // 监听DOM变化(用于动态加载的页面) observeDOMChanges() { const observer = new MutationObserver((mutations) => { // 检查是否需要重新应用模式 let style = document.querySelector('style[id^="eye-protect-"]'); if (!style && this.shouldApplyMode()) { setTimeout(() => this.applyMode(), 100); } }); // 监听整个文档树的变化 observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style', 'id'] }); }, // 判断是否为白天 isDaytime() { let time = this.getConfig('customDayNight').split('|'); let now = new Date(); let currentTime = now.getHours() * 60 + now.getMinutes(); let dayStart = this.timeToMinutes(time[0]); let dayEnd = this.timeToMinutes(time[1]); if (dayStart < dayEnd) { return currentTime >= dayStart && currentTime < dayEnd; } else { return currentTime >= dayStart || currentTime < dayEnd; } }, timeToMinutes(timeStr) { let parts = timeStr.split(':'); return parseInt(parts[0]) * 60 + parseInt(parts[1] || 0); }, // 获取当前应该应用的模式 getCurrentMode() { // 首先检查网站专用模式(最高优先级) let siteSpecificMode = this.getSiteSpecificMode(); if (siteSpecificMode && siteSpecificMode !== 'default') { return siteSpecificMode; } // 确保只有有效模式 let mode = this.currentMode || this.getConfig('currentMode'); const validGlobalModes = ['light', 'dark', 'classic', 'soft', 'eyecare', 'filter', 'green', 'warm']; if (!validGlobalModes.includes(mode)) { mode = 'light'; this.setConfig('currentMode', 'light'); } // 如果启用了自动切换(只处理0/1,0=白天 1=夜间) if (this.getConfig('autoSwitch')) { let modes = this.getConfig('autoSwitch').split('|'); if (modes.length === 2) { // 自动切换中,值0=light,1=dark if (this.isDaytime()) { mode = modes[0] === '1' ? 'dark' : 'light'; } else { mode = modes[1] === '1' ? 'dark' : 'light'; } } } // 如果跟随浏览器暗色模式 if (this.getConfig('darkAuto') && window.matchMedia) { let prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (prefersDark && mode === 'light') { mode = 'dark'; } } return mode; }, // 彻底清理所有样式和修改 cleanupAllStyles() { // 1. 移除所有样式标签 let styles = document.querySelectorAll('style[id^="eye-protect-"]'); styles.forEach(style => { style.remove(); }); // 2. 移除滤镜模式的SVG let svg = document.getElementById('eye-protect-filter-svg'); if (svg) svg.remove(); // 3. 恢复原始主题色 let meta = document.querySelector('meta[name="theme-color"]'); if (meta) { meta.content = this.getConfig('originThemeColor'); } // 4. 移除动态添加的内联样式(原有) let oldElements = document.querySelectorAll('[data-eye-protect]'); oldElements.forEach(element => { element.removeAttribute('data-eye-protect'); element.removeAttribute('style'); }); // 5. 移除滤镜模式特定样式 let filterStyle = document.getElementById('eye-protect-filter-style'); if (filterStyle) filterStyle.remove(); // 6. 移除绿色蒙层和暖色蒙层 this.removeGreenOverlay(); this.removeWarmOverlay(); }, // 应用样式(带模式标识) applyStyle(css, modeId) { // 移除所有之前可能存在的样式 this.cleanupAllStyles(); let style = document.createElement('style'); style.id = 'eye-protect-' + modeId; style.setAttribute('data-eye-protect-mode', modeId); style.innerHTML = css; // 确保文档已准备好 if (document.head) { document.head.appendChild(style); } else { // 如果head不存在,等待它加载 const waitForHead = () => { if (document.head) { document.head.appendChild(style); } else { requestAnimationFrame(waitForHead); } }; waitForHead(); } // 标记body,方便调试 if (document.body) { document.body.setAttribute('data-eye-protect', 'enabled'); document.body.setAttribute('data-eye-mode', modeId); } }, // 从用户输入中提取主机名(支持完整网址或纯域名) extractHostname(input) { if (!input) return ''; // 尝试使用URL API解析 try { // 如果输入不包含协议,添加一个虚拟协议以便URL解析 let urlString = input; if (!input.startsWith('http://') && !input.startsWith('https://')) { urlString = 'http://' + input; } let url = new URL(urlString); return url.hostname; } catch (e) { // 如果解析失败,尝试简单清理 // 移除协议部分 let cleaned = input.replace(/^https?:\/\//i, '').split('/')[0].split('?')[0].split('#')[0]; return cleaned; } }, // 获取网站专用模式(返回模式字符串或null,若是豆沙绿或深暖色则返回包含mode和opacity的对象) // 如果全局临时关闭所有专用模式开关开启,则返回 null getSiteSpecificMode(domain) { // 检查全局临时关闭所有专用模式 if (this.getConfig('tempDisableAllSiteModes')) { return null; } let host = domain || location.host; try { let siteModesStr = this.getConfig('siteSpecificModes') || '{}'; let siteModes = JSON.parse(siteModesStr); let entry = siteModes[host]; if (!entry) return null; // 兼容旧格式:如果是字符串,直接返回 if (typeof entry === 'string') return entry; // 新格式:对象,返回mode(豆沙绿/深暖色需要额外参数,上层调用需判断) if (typeof entry === 'object' && entry.mode) { return entry; // 返回完整对象,上层调用需判断 } return null; } catch (e) { return null; } }, // 设置网站专用模式(mode为模式字符串,如果为green或warm且需要opacity,则存储对象) setSiteSpecificMode(domain, mode, opacity = null) { try { let host = domain; if (!host) return; let siteModesStr = this.getConfig('siteSpecificModes') || '{}'; let siteModes = JSON.parse(siteModesStr); if (mode === 'default' || mode === null) { delete siteModes[host]; } else if (mode === 'green' || mode === 'warm') { // 存储对象 let existing = siteModes[host]; let currentOpacity = (existing && typeof existing === 'object' && existing.opacity) ? existing.opacity : (mode === 'green' ? 0.3 : 0.3); if (opacity !== null) currentOpacity = opacity; siteModes[host] = { mode: mode, opacity: currentOpacity }; } else { siteModes[host] = mode; } this.setConfig('siteSpecificModes', JSON.stringify(siteModes)); } catch (e) { console.error('设置网站专用模式出错:', e); } }, // 获取豆沙绿模式在当前网站的浓度(专用模式优先,否则返回全局浓度) getGreenOpacityForSite(domain) { let host = domain || location.host; try { let siteModesStr = this.getConfig('siteSpecificModes') || '{}'; let siteModes = JSON.parse(siteModesStr); let entry = siteModes[host]; if (entry && typeof entry === 'object' && entry.mode === 'green' && typeof entry.opacity === 'number') { return entry.opacity; } // 无专用模式,返回全局浓度 return this.getConfig('globalGreenOpacity') || 0.3; } catch (e) { return 0.3; } }, // 设置豆沙绿浓度(专用模式) setGreenOpacityForSite(domain, opacity) { let host = domain || location.host; try { let siteModesStr = this.getConfig('siteSpecificModes') || '{}'; let siteModes = JSON.parse(siteModesStr); let entry = siteModes[host]; if (entry && typeof entry === 'object' && entry.mode === 'green') { entry.opacity = Math.min(0.95, Math.max(0, opacity)); siteModes[host] = entry; } else if (entry === 'green') { // 旧格式,转换为对象 siteModes[host] = { mode: 'green', opacity: Math.min(0.95, Math.max(0, opacity)) }; } else { // 未设置,先创建 siteModes[host] = { mode: 'green', opacity: Math.min(0.95, Math.max(0, opacity)) }; } this.setConfig('siteSpecificModes', JSON.stringify(siteModes)); } catch (e) { console.error('设置豆沙绿浓度出错:', e); } }, // 获取深暖色模式在当前网站的浓度(专用模式优先,否则返回全局浓度) getWarmOpacityForSite(domain) { let host = domain || location.host; try { let siteModesStr = this.getConfig('siteSpecificModes') || '{}'; let siteModes = JSON.parse(siteModesStr); let entry = siteModes[host]; if (entry && typeof entry === 'object' && entry.mode === 'warm' && typeof entry.opacity === 'number') { return entry.opacity; } // 无专用模式,返回全局浓度 return this.getConfig('globalWarmOpacity') || 0.3; } catch (e) { return 0.3; } }, // 设置深暖色浓度(专用模式) setWarmOpacityForSite(domain, opacity) { let host = domain || location.host; try { let siteModesStr = this.getConfig('siteSpecificModes') || '{}'; let siteModes = JSON.parse(siteModesStr); let entry = siteModes[host]; if (entry && typeof entry === 'object' && entry.mode === 'warm') { entry.opacity = Math.min(0.95, Math.max(0, opacity)); siteModes[host] = entry; } else if (entry === 'warm') { // 旧格式,转换为对象 siteModes[host] = { mode: 'warm', opacity: Math.min(0.95, Math.max(0, opacity)) }; } else { // 未设置,先创建 siteModes[host] = { mode: 'warm', opacity: Math.min(0.95, Math.max(0, opacity)) }; } this.setConfig('siteSpecificModes', JSON.stringify(siteModes)); } catch (e) { console.error('设置深暖色浓度出错:', e); } }, // ================ 豆沙绿模式(源自绿黄黑护眼模式) ================ applyGreenMode() { // 先清理其他样式 this.cleanupAllStyles(); // 获取当前网站的浓度(专用模式优先) let opacity = this.getGreenOpacityForSite(); // 创建或更新蒙层 if (!this.greenOverlay) { this.greenOverlay = document.createElement('div'); this.greenOverlay.id = 'eye-protect-green-overlay'; this.greenOverlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 2147483500; pointer-events: none; transition: background 0.2s, opacity 0.2s; mix-blend-mode: multiply; `; document.documentElement.appendChild(this.greenOverlay); } this.greenOverlay.style.backgroundColor = '#C7EDCC'; this.greenOverlay.style.opacity = opacity; this.greenOverlay.style.display = 'block'; // 设置主题色 this.applyThemeColor('#C7EDCC'); }, // 更新豆沙绿浓度(专用模式) updateGreenOpacity(opacity) { if (this.greenOverlay) { this.greenOverlay.style.opacity = opacity; } // 保存到当前网站的配置 this.setGreenOpacityForSite(location.host, opacity); }, // 更新全局豆沙绿浓度(并刷新当前应用) updateGlobalGreenOpacity(opacity) { this.setConfig('globalGreenOpacity', Math.min(0.95, Math.max(0, opacity))); // 如果当前全局模式为豆沙绿且没有专用模式覆盖,则重新应用 let siteSpecific = this.getSiteSpecificMode(); let modeToApply = siteSpecific ? (typeof siteSpecific === 'string' ? siteSpecific : siteSpecific.mode) : null; if (!modeToApply && this.currentMode === 'green') { this.applyGreenMode(); // 重新应用以使用新浓度 } else if (modeToApply === 'green') { // 如果当前有专用模式且为豆沙绿,也要刷新(因为专用模式浓度优先,但我们需要重新读取专用浓度) this.applyMode(); } }, // 移除绿色蒙层 removeGreenOverlay() { if (this.greenOverlay && this.greenOverlay.parentNode) { this.greenOverlay.remove(); this.greenOverlay = null; } }, // ================ 深暖色模式(源自绿黄黑护眼模式) ================ applyWarmMode() { // 先清理其他样式 this.cleanupAllStyles(); // 获取当前网站的浓度(专用模式优先) let opacity = this.getWarmOpacityForSite(); // 创建或更新蒙层 if (!this.warmOverlay) { this.warmOverlay = document.createElement('div'); this.warmOverlay.id = 'eye-protect-warm-overlay'; this.warmOverlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 2147483500; pointer-events: none; transition: background 0.2s, opacity 0.2s; mix-blend-mode: multiply; `; document.documentElement.appendChild(this.warmOverlay); } this.warmOverlay.style.backgroundColor = '#CFB988'; this.warmOverlay.style.opacity = opacity; this.warmOverlay.style.display = 'block'; // 设置主题色 this.applyThemeColor('#CFB988'); }, // 更新深暖色浓度(专用模式) updateWarmOpacity(opacity) { if (this.warmOverlay) { this.warmOverlay.style.opacity = opacity; } // 保存到当前网站的配置 this.setWarmOpacityForSite(location.host, opacity); }, // 更新全局深暖色浓度(并刷新当前应用) updateGlobalWarmOpacity(opacity) { this.setConfig('globalWarmOpacity', Math.min(0.95, Math.max(0, opacity))); // 如果当前全局模式为深暖色且没有专用模式覆盖,则重新应用 let siteSpecific = this.getSiteSpecificMode(); let modeToApply = siteSpecific ? (typeof siteSpecific === 'string' ? siteSpecific : siteSpecific.mode) : null; if (!modeToApply && this.currentMode === 'warm') { this.applyWarmMode(); // 重新应用以使用新浓度 } else if (modeToApply === 'warm') { this.applyMode(); } }, // 移除深暖色蒙层 removeWarmOverlay() { if (this.warmOverlay && this.warmOverlay.parentNode) { this.warmOverlay.remove(); this.warmOverlay = null; } }, // ================ 柔和护眼模式(来自暗色模式v3.1.9 的 soft 主题) ================ applySoftMode() { // 柔和护眼配置:invert:1, hue:180, bright:0.95, contrast:0.9, saturate:0.8, bg:"#121212" const inv = 1; const hue = 180; const bright = 0.95; const contrast = 0.9; const saturate = 0.8; const bg = "#121212"; const filter = `invert(${inv}) hue-rotate(${hue}deg) brightness(${bright}) contrast(${contrast}) saturate(${saturate})`; const mediaFilter = `invert(${inv}) hue-rotate(${hue}deg) brightness(${bright}) contrast(${contrast}) saturate(${saturate})`; const css = ` /* 柔和护眼模式(来自暗色模式v3.1.9) */ html { filter: ${filter} !important; background: ${bg} !important; will-change: filter, background; } img, video, iframe, canvas, [style*="background-image"] { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } img[src*=".svg"], svg { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } video::-webkit-media-controls-panel, video::-webkit-media-controls { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } picture, source { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } a { color: #0066cc !important; } `; this.applyStyle(css, 'soft-mode'); this.applyThemeColor(bg); }, // ================ 原版深色模式(经典反色,来自暗色模式v3.1.9) ================ applyClassicDarkMode() { const inv = 1; const hue = 180; const bright = 1; const contrast = 1; const saturate = 1; const bg = "#000"; const filter = `invert(${inv}) hue-rotate(${hue}deg) brightness(${bright}) contrast(${contrast}) saturate(${saturate})`; const mediaFilter = `invert(${inv}) hue-rotate(${hue}deg) brightness(${bright}) contrast(${contrast}) saturate(${saturate})`; const css = ` /* 原版深色模式(经典反色) */ html { filter: ${filter} !important; background: ${bg} !important; will-change: filter, background; } img, video, iframe, canvas, [style*="background-image"] { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } img[src*=".svg"], svg { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } video::-webkit-media-controls-panel, video::-webkit-media-controls { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } picture, source { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } a { color: #0066cc !important; } `; this.applyStyle(css, 'classic-mode'); this.applyThemeColor(bg); }, // ================ 护眼暗色模式(来自暗色模式v3.1.9) ================ applyEyeCareMode() { // 护眼暗色配置:invert:1, hue:180, bright:0.92, contrast:0.95, saturate:0.9, bg:"#111" const inv = 1; const hue = 180; const bright = 0.92; const contrast = 0.95; const saturate = 0.9; const bg = "#111"; const filter = `invert(${inv}) hue-rotate(${hue}deg) brightness(${bright}) contrast(${contrast}) saturate(${saturate})`; const mediaFilter = `invert(${inv}) hue-rotate(${hue}deg) brightness(${bright}) contrast(${contrast}) saturate(${saturate})`; const css = ` /* 护眼暗色模式(来自暗色模式v3.1.9) */ html { filter: ${filter} !important; background: ${bg} !important; will-change: filter, background; } img, video, iframe, canvas, [style*="background-image"] { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } img[src*=".svg"], svg { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } video::-webkit-media-controls-panel, video::-webkit-media-controls { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } picture, source { filter: ${mediaFilter} !important; -webkit-filter: ${mediaFilter} !important; } a { color: #0066cc !important; } `; this.applyStyle(css, 'eyecare-mode'); this.applyThemeColor(bg); }, // 应用通用夜间模式(原反色模式,更名为夜间模式) applyOriginalDarkMode() { let style_30 = this.getConfig('customDark3') || '90'; let dark3Exclude = this.getConfig('dark3Exclude'); let style_31 = ` html { filter: invert(${style_30}%) !important; text-shadow: 0 0 0 !important; } ${dark3Exclude} { filter: invert(1) !important; } img[alt="[公式]"] { filter: none !important; } /* 滚动条样式 */ ::-webkit-scrollbar { height: 12px !important; width: 12px !important; } ::-webkit-scrollbar-thumb { border-radius: 0; border-color: transparent; border-style: dashed; background-color: #3f4752 !important; background-clip: padding-box; transition: background-color .32s ease-in-out; } ::-webkit-scrollbar-corner { background: #202020 !important; } ::-webkit-scrollbar-track { background-color: #22272e !important; } ::-webkit-scrollbar-thumb:hover { background: #3f4752 !important; } `; if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) { style_31 = ` html { filter: invert(${style_30}%) !important; background-image: url(); text-shadow: 0 0 0 !important; } ${dark3Exclude} { filter: invert(1) !important; } img[alt="[公式]"] { filter: none !important; } `; } this.applyStyle(style_31, 'dark-mode-original'); }, // 判断是否为Firefox浏览器 isFirefox() { return /Firefox/i.test(navigator.userAgent); }, // 应用滤镜模式 applyFilterMode() { // 首先检查是否应该排除当前网站 let filterExcludeList = this.getConfig('filterExcludeList'); let host = location.host; if (filterExcludeList.includes(host)) { this.cleanupAllStyles(); return; } // 彻底清理所有样式 this.cleanupAllStyles(); // 创建SVG滤镜(非Firefox浏览器) if (!this.isFirefox() && !document.getElementById('eye-protect-filter-svg')) { this.createFilterSvg(); } // 创建滤镜样式 this.createFilterStyle(); // 设置深色主题色 this.applyThemeColor('#131313'); }, // 创建SVG滤镜 createFilterSvg() { let svgDom = ''; let div = document.createElementNS('http://www.w3.org/2000/svg', 'div'); div.innerHTML = svgDom; let frag = document.createDocumentFragment(); while (div.firstChild) frag.appendChild(div.firstChild); document.head.appendChild(frag); }, // 创建滤镜样式 createFilterStyle() { let filter = this.isFirefox() ? `filter: url('data:image/svg+xml;utf8,#eye-protect-filter') !important;` : '-webkit-filter: url(#eye-protect-filter) !important; filter: url(#eye-protect-filter) !important;'; let reverseFilter = this.isFirefox() ? `filter: url('data:image/svg+xml;utf8,#eye-protect-filter-reverse') !important;` : '-webkit-filter: url(#eye-protect-filter-reverse) !important; filter: url(#eye-protect-filter-reverse) !important;'; let css = ` @media screen { html { ${filter} scrollbar-color: #454a4d #202324; } /* Default Reverse rule */ img, video, iframe, canvas, :not(object):not(body) > embed, object, svg image, [style*="background:url"], [style*="background-image:url"], [style*="background: url"], [style*="background-image: url"], [background], twitterwidget, .no-dark-mode, .sr-reader, .sr-backdrop { ${reverseFilter} } [style*="background:url"] *, [style*="background-image:url"] *, [style*="background: url"] *, [style*="background-image: url"] *, input, [background] *, img[src^="https://s0.wp.com/latex.php"], twitterwidget .NaturalImage-image { -webkit-filter: none !important; filter: none !important; } /* Text contrast */ html { text-shadow: 0 0 0 !important; } /* Full screen */ .no-filter, :-webkit-full-screen, :-webkit-full-screen *, :-moz-full-screen, :-moz-full-screen *, :fullscreen, :fullscreen * { -webkit-filter: none !important; filter: none !important; } ::-webkit-scrollbar { background-color: #202324; color: #aba499; } ::-webkit-scrollbar-thumb { background-color: #454a4d; } ::-webkit-scrollbar-thumb:hover { background-color: #575e62; } ::-webkit-scrollbar-thumb:active { background-color: #484e51; } ::-webkit-scrollbar-corner { background-color: #181a1b; } /* Page background */ html { background: #fff !important; } } `; let style = document.createElement('style'); style.id = 'eye-protect-filter-style'; style.innerHTML = css; document.head.appendChild(style); }, // 禁用滤镜模式 disableFilterMode() { let style = document.getElementById('eye-protect-filter-style'); if (style) style.remove(); let svg = document.getElementById('eye-protect-filter-svg'); if (svg) svg.remove(); // 恢复原始主题色 this.applyThemeColor(this.getConfig('originThemeColor') || '#ffffff'); }, // 应用夜间模式(原反色模式,已移除123云盘专用规则) applyDarkMode() { // 彻底清理所有样式 this.cleanupAllStyles(); // 根据域名使用不同的处理逻辑 let hostname = window.location.hostname; // 检查网站专用模式 let siteSpecificMode = this.getSiteSpecificMode(); if (siteSpecificMode === 'light') { // 如果网站专用模式是白天模式,清理样式 this.cleanupAllStyles(); return; } // 检查是否为JSON文件页面或特定的代码网页 if (this.isJsonOrCodePage(window.location.href)) { this.applyCodePageDarkMode(); } else if (hostname.includes('bilivod.com') || hostname.includes('bilivod2.cc') || hostname.includes('bilitv.top')) { this.applyBilivodDarkMode(); } else if (hostname.includes('lanzou')) { this.applyLanzouDarkMode(); } else { // 对于所有其他网站,使用通用夜间模式 this.applyOriginalDarkMode(); } // 设置主题色为深色 this.applyThemeColor('#131313'); }, // 判断是否为JSON文件页面或特定的代码页面(已增强:支持.js和.user.js文件) isJsonOrCodePage(url) { // 新增:检查是否为JavaScript文件(以.js或.user.js结尾,包括查询参数) if (url.endsWith('.js') || url.includes('.js?') || url.includes('.js#') || url.endsWith('.user.js') || url.includes('.user.js?')) { return true; } // 检查URL是否以.json结尾或包含.json if (url.endsWith('.json') || url.includes('.json?') || url.includes('.json#') || url.includes('.json&') || url.includes('.json/') || url.indexOf('.json') > -1) { return true; } // 检查页面内容类型是否为JSON const contentType = document.contentType || ''; if (contentType.includes('application/json') || contentType.includes('text/json')) { return true; } // 检查是否为特定的GitHub Raw文件代理 if (url.includes('edgeone.gh-proxy.org') && url.includes('raw.githubusercontent.com')) { return true; } // 检查是否为gh-proxy.org代理的GitHub Raw文件 if (url.includes('gh-proxy.org') && url.includes('raw.githubusercontent.com')) { return true; } // 检查是否为JSON配置文件 if (url.includes('shuyuan.xingmian.icu') && url.includes('/data/shuyuan/') && url.endsWith('.json')) { return true; } // 检查是否为dns.jingluo.love的JSON文件 if (url.includes('dns.jingluo.love') && url.endsWith('.json')) { return true; } // 检查scriptcat.org代码页面 if (url.includes('scriptcat.org') && url.includes('/code/')) { return true; } // 检查updategf.qytechs.cn代码页面 if (url.includes('updategf.qytechs.cn') && url.includes('/scripts/')) { return true; } // 检查页面内容是否为有效的JSON格式 try { if (document.body) { const bodyText = document.body.textContent.trim(); if ((bodyText.startsWith('{') && bodyText.endsWith('}')) || (bodyText.startsWith('[') && bodyText.endsWith(']'))) { JSON.parse(bodyText); return true; } } // 检查pre元素中的内容 const preElements = document.querySelectorAll('pre'); for (let pre of preElements) { const text = pre.textContent.trim(); if ((text.startsWith('{') && text.endsWith('}')) || (text.startsWith('[') && text.endsWith(']'))) { try { JSON.parse(text); return true; } catch (e) { // 不是有效的JSON } } } } catch (e) { // 不是有效的JSON,继续检查其他条件 } return false; }, // 应用主题色 applyThemeColor(color) { setTimeout(() => { let meta = document.querySelector('meta[name="theme-color"]'); if (meta) { meta.content = color; } else { let metaEle = document.createElement('meta'); metaEle.name = 'theme-color'; metaEle.content = color; if (document.head) { document.head.appendChild(metaEle); } } }, 100); }, // 通用代码页面夜间模式(用于JSON文件和特定代码页面) applyCodePageDarkMode() { let style_30 = this.getConfig('customDark3') || '90'; let css = ` /* 通用代码页面专用夜间模式(适用于JSON文件和代码页面) */ html { filter: invert(${style_30}%) hue-rotate(180deg) !important; background-color: #0d1117 !important; text-shadow: 0 0 0 !important; } /* 排除需要保持原始颜色的元素 */ img, svg, canvas, iframe, .icon, .logo, .avatar, [class*="icon"], [class*="Icon"], [src*=".svg"], [src*=".png"], [src*=".jpg"], [src*=".jpeg"], [src*=".gif"], [src*=".webp"], .code-preview img, .avatar img, pre img, code img, .hljs img { filter: invert(1) hue-rotate(180deg) !important; } body, html { background-color: #0d1117 !important; color: #c9d1d9 !important; } /* 代码查看器主区域 */ .container, .main-container, .wrapper, .content, .code-container, .script-container, .page-content, .main-content, body > div:not([class]):not([id]), div[class=""], div[id=""] { background-color: #0d1117 !important; color: #c9d1d9 !important; } /* 代码查看器头部 */ .header, .script-header, .page-header, .navbar, .nav, .top-bar, [class*="header"], [class*="Header"], .breadcrumb, .nav-bar, .header-bar, .title-bar { background-color: #161b22 !important; border-color: #30363d !important; color: #c9d1d9 !important; } /* 代码高亮区域 */ .code-viewer, .code-area, .editor-container, pre, code, .hljs, .prismjs, [class*="language-"], [class*="syntax-"], .CodeMirror, .cm-s-default, .code-mirror, .code-editor, .editor, .viewer, .source-code, .code-block, .code-snippet, div[style*="font-family:monospace"], div[style*="font-family: monospace"], div[style*="font-family:'Courier New'"], div[style*="font-family: 'Courier New'"], div[style*="font-family:Consolas"], div[style*="font-family: Consolas"], div[style*="font-family:Monaco"], div[style*="font-family: Monaco"] { background-color: #0d1117 !important; color: #c9d1d9 !important; } /* 代码行号和语法高亮 */ .line-numbers, .line-number, .linenumber, .ln, .lnum, td[class*="line"], td[class*="number"], td[style*="text-align:right"], td[style*="text-align: right"] { background-color: #161b22 !important; color: #8b949e !important; border-right-color: #30363d !important; } /* 代码关键字高亮 */ .keyword, .function, .class-name, .operator, .punctuation, .string, .comment, .variable, .constant, .built_in, .attr-name, .selector, .property, .number, .boolean { color: #d2a8ff !important; } /* JSON语法高亮增强 */ /* JSON键(字符串) */ span[style*="color: #000080"], span[style*="color:#000080"], span[style*="color: #001080"], span[style*="color:#001080"], .string, [class*="key"], [style*="color: rgb(0, 0, 128)"], [style*="color:rgb(0,0,128)"] { color: #79c0ff !important; font-weight: normal !important; } /* JSON字符串值 */ span[style*="color: #0451a5"], span[style*="color:#0451a5"], span[style*="color: #a31515"], span[style*="color:#a31515"], .value.string, [style*="color: rgb(4, 81, 165)"], [style*="color:rgb(4,81,165)"], [style*="color: rgb(163, 21, 21)"], [style*="color:rgb(163,21,21)"] { color: #a5d6ff !important; } /* JSON数字值 */ span[style*="color: #098658"], span[style*="color:#098658"], .number, [style*="color: rgb(9, 134, 88)"], [style*="color:rgb(9,134,88)"] { color: #56d364 !important; } /* JSON布尔值和null */ span[style*="color: #0000ff"], span[style*="color:#0000ff"], .boolean, .null, [style*="color: rgb(0, 0, 255)"], [style*="color:rgb(0,0,255)"] { color: #ff7b72 !important; } /* JSON标点符号(冒号、括号、逗号) */ span[style*="color: #000000"], span[style*="color:#000000"], .punctuation, [style*="color: rgb(0, 0, 0)"], [style*="color:rgb(0,0,0)"] { color: #c9d1d9 !important; } /* 用户脚本元数据区域 */ pre[style*="color: rgb(136, 136, 136)"], pre[style*="color:rgb(136,136,136)"], pre[style*="color:#888"], .user.js-header, .metadata-block, .meta, .userscript-meta { background-color: #161b22 !important; color: #8b949e !important; border: 1px solid #30363d !important; } /* 脚本说明区域 */ .description, .desc, .summary, .comment-block, .doc-block, [style*="color: rgb(102, 102, 102)"], [style*="color:rgb(102,102,102)"], [style*="color:#666"] { color: #8b949e !important; } /* 滚动条优化 */ ::-webkit-scrollbar { width: 12px !important; height: 12px !important; background-color: #0d1117 !important; } ::-webkit-scrollbar-thumb { background-color: #30363d !important; border: 2px solid #0d1117 !important; border-radius: 6px !important; } ::-webkit-scrollbar-thumb:hover { background-color: #444c56 !important; } ::-webkit-scrollbar-track { background-color: #161b22 !important; } ::-webkit-scrollbar-corner { background-color: #0d1117 !important; } /* 链接和按钮 */ a, a:link, a:visited { color: #58a6ff !important; } a:hover, a:active { color: #79b8ff !important; } button, .btn, .button, input[type="button"], input[type="submit"] { background-color: #238636 !important; color: #ffffff !important; border-color: #2ea043 !important; } button:hover, .btn:hover { background-color: #2ea043 !important; } /* 文本选择 */ ::selection { background-color: #1c6b48 !important; color: #ffffff !important; } /* Firefox兼容性 */ @-moz-document url-prefix() { html { filter: invert(${style_30}%) hue-rotate(180deg) !important; background-image: url() !important; background-color: #0d1117 !important; } /* Firefox滚动条 */ * { scrollbar-color: #30363d #0d1117 !important; scrollbar-width: thin !important; } } /* 通用文本颜色覆盖 */ p, span, div, h1, h2, h3, h4, h5, h6, li, label, strong, em, i, b, u, .text, .title, .name, .info, .desc, .content, .label, .tip, .hint, .help, [class*="text"], [class*="Text"], [class*="title"], [class*="Title"], [class*="name"], [class*="Name"], [class*="label"], [class*="Label"], [class*="desc"], [class*="Desc"], [class*="info"], [class*="Info"], [class*="content"], [class*="Content"], [class*="tip"], [class*="Tip"], [class*="hint"], [class*="Hint"], [class*="help"], [class*="Help"] { color: #c9d1d9 !important; } /* 输入框和表单 */ input, textarea, select, [class*="input"], [class*="Input"] { background-color: #0d1117 !important; color: #c9d1d9 !important; border-color: #30363d !important; } input::placeholder, textarea::placeholder { color: #8b949e !important; } /* JSON特定元素高亮增强 */ /* 确保所有pre元素都有正确的样式 */ pre { padding: 16px !important; border-radius: 6px !important; background-color: #161b22 !important; border: 1px solid #30363d !important; overflow-x: auto !important; margin: 1em 0 !important; } /* JSON行内高亮 */ span { transition: color 0.2s ease !important; } /* JSON折叠指示器 */ .json-formatter-row .json-formatter-toggle { color: #58a6ff !important; } `; this.applyStyle(css, 'dark-mode-code-page'); }, // bilivod.com及同类域名专用夜间模式 applyBilivodDarkMode() { let style_30 = this.getConfig('customDark3') || '90'; let css = ` /* bilivod.com及同类域名专用夜间模式 */ html { filter: invert(${style_30}%) hue-rotate(180deg) !important; background-color: #1a1a1a !important; text-shadow: 0 0 0 !important; } /* 排除不需要反色的元素 */ img, svg, video, canvas, iframe, .icon, .logo, .thumbnail, .avatar, [class*="icon"], [class*="Icon"], [src*=".svg"], [src*=".png"], [src*=".jpg"], [src*=".jpeg"], [src*=".gif"], [src*=".webp"], .video-player, .player, [class*="player"], [class*="Player"] { filter: invert(1) hue-rotate(180deg) !important; } body { background-color: #1a1a1a !important; } /* bilivod.com特定优化 */ body, div, section, main, article, nav, header, footer, aside, .container, .wrapper, .content, .main-content, .panel, .card, .box, .block, .list, .item, .entry, .video-list, .video-item, .vod-item { background-color: #1a1a1a !important; color: #d0d0d0 !important; border-color: #333 !important; } /* 强力覆盖所有内联白色背景 */ [style*="background-color: white"], [style*="background: white"], [style*="background:#fff"], [style*="background-color:#fff"], [style*="background-color: #fff"], [style*="background-color: #ffffff"] { background-color: #1a1a1a !important; } /* 导航栏和头部 */ .header, .navbar, .nav, .top-bar, .menu, .toolbar, .bar { background-color: #222 !important; } /* 按钮和表单 */ button, .btn, .button, input, textarea, select { background-color: #2a2a2a !important; color: #e0e0e0 !important; border-color: #444 !important; } /* 链接和蓝色区域 */ a, a:link, a:visited { color: #ff7700 !important; } a:hover { color: #ff9933 !important; } .btn-primary, .primary, .blue { background-color: #ff7700 !important; } /* 滚动条 */ ::-webkit-scrollbar { background-color: #1a1a1a !important; } ::-webkit-scrollbar-thumb { background-color: #444 !important; } ::-webkit-scrollbar-thumb:hover { background-color: #555 !important; } ::-webkit-scrollbar-track { background-color: #222 !important; } /* Firefox兼容性 */ @-moz-document url-prefix() { html { filter: invert(${style_30}%) hue-rotate(180deg) !important; background-image: url() !important; background-color: #1a1a1a !important; } } `; this.applyStyle(css, 'dark-mode-bilivod'); }, // 蓝奏云优化的夜间模式 applyLanzouDarkMode() { let style_30 = this.getConfig('customDark3') || '90'; let css = ` /* 蓝奏云夜间模式优化版(来自版本5.6.15) */ html { filter: invert(${style_30}%) hue-rotate(180deg) !important; background-color: #1a1a1a !important; text-shadow: 0 0 0 !important; } img, svg, video, canvas, iframe, .icon, .logo, .qrcode, .code-img { filter: invert(1) hue-rotate(180deg) !important; } body, html { background-color: #1a1a1a !important; color: #e0e0e0 !important; } div, section, main, article, nav, header, footer, aside, .container, .wrapper, .content, .main, .panel, .box { background-color: #1a1a1a !important; color: #e0e0e0 !important; } .file-list, .folder-list, .data-list, .table, .list-container, .list-box { background-color: #222 !important; border: 1px solid #333 !important; } .file-item, .folder-item, .list-item, tr, .data-item, .list-row { background-color: #2a2a2a !important; color: #e0e0e0 !important; border-color: #333 !important; } button, .btn, input[type="button"], input[type="submit"], .download-btn { background-color: #0066cc !important; color: white !important; border: 1px solid #0066cc !important; } button:hover, .btn:hover, .download-btn:hover { background-color: #0080ff !important; border-color: #0080ff !important; } input, textarea, select, .form-control { background-color: #2a2a2a !important; color: #e0e0e0 !important; border: 1px solid #444 !important; } a, a:link, a:visited { color: #66aaff !important; } a:hover, a:active { color: #88ccff !important; } ::-webkit-scrollbar { width: 12px !important; height: 12px !important; background-color: #222 !important; } ::-webkit-scrollbar-thumb { background-color: #444 !important; border-radius: 6px !important; } @-moz-document url-prefix() { html { filter: invert(${style_30}%) hue-rotate(180deg) !important; background-image: url() !important; background-color: #1a1a1a !important; } } `; this.applyStyle(css, 'dark-mode-lanzou'); }, // 获取元素的有效背景色(向上查找直到找到非透明背景) getEffectiveBackgroundColor(element) { let el = element; while (el && el !== document.documentElement) { const bg = window.getComputedStyle(el).backgroundColor; if (bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') { return bg; } el = el.parentElement; } return window.getComputedStyle(document.documentElement).backgroundColor; }, // 是否应该排除当前网站(智能排除 - 仅排除明显深色背景的网站) shouldExcludeSite() { let autoExclude = this.getConfig('autoExclude'); if (!autoExclude) return false; // 未开启智能排除 let host = location.host; let enableList = this.getConfig('enableList'); // 白名单中的网站不排除 if (enableList.includes(host)) { return false; } let hostname = window.location.hostname; // 对于特定网站,我们使用专门的处理逻辑(这些网站即使自带暗色模式,也可能需要处理,暂时不排除) if (hostname.includes('bilivod.com') || hostname.includes('bilivod2.cc') || hostname.includes('bilitv.top') || hostname.includes('lanzou') || hostname.includes('scriptcat.org') || hostname.includes('updategf.qytechs.cn')) { return false; } let forcedList = this.getConfig('forcedEnableList'); if (forcedList.includes(host)) { return false; } // 通用排除逻辑:检测网站是否已自带深色模式 let html = document.documentElement; let body = document.body; // 1. 检查 meta 标签 color-scheme let colorSchemeMeta = document.querySelector('meta[name="color-scheme"]'); if (colorSchemeMeta && colorSchemeMeta.content && colorSchemeMeta.content.toLowerCase().includes('dark')) { return true; } // 2. 检查 class 或属性 if (html && (html.className.toLowerCase().includes('dark') || (body && body.className.toLowerCase().includes('dark')))) { return true; } // 3. 检查 data-theme 属性 if (html && html.getAttribute('data-theme') && html.getAttribute('data-theme').toLowerCase().includes('dark')) { return true; } // 4. 检查 data-color-mode 属性 if (html && html.getAttribute('data-color-mode') && html.getAttribute('data-color-mode').toLowerCase().includes('dark')) { return true; } // 5. 检查页面背景色(仅排除明显深色背景,RGB总和小于150,避免误判浅色网站) try { // 获取 body 或 html 的有效背景色(向上查找直到非透明) let bgElement = body || html; let bgColor = this.getEffectiveBackgroundColor(bgElement); // 解析 RGB 值 let rgbMatch = bgColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/); if (rgbMatch) { let r = parseInt(rgbMatch[1]); let g = parseInt(rgbMatch[2]); let b = parseInt(rgbMatch[3]); // 亮度判断:RGB 总和小于 150 才视为深色背景,严格排除误判 if (r + g + b < 150) { return true; } } } catch (e) { // 忽略背景色检测错误 } return false; }, // 是否应该应用模式 shouldApplyMode() { let globalEnable = this.getConfig('globalEnable'); let enableList = this.getConfig('enableList'); let blacklist = this.getConfig('blacklist'); let forcedList = this.getConfig('forcedEnableList'); let host = location.host; // 第一步:检查黑名单(最高优先级) if (blacklist.includes(host)) { return false; } // 第二步:检查强制启用列表(修改:受全局开关和白名单影响) if (forcedList.includes(host)) { // 强制启用需要满足:全局开关开启 或 该网站在白名单中 if (globalEnable || enableList.includes(host)) { return true; } return false; } // 第三步:检查全局开关 if (globalEnable) { return true; } // 第四步:检查白名单 if (enableList.includes(host)) { return true; } // 都不满足,不应用模式 return false; }, // 切换黑名单状态 toggleBlacklist() { let blacklist = this.getConfig('blacklist'); let host = location.host; if (blacklist.includes(host)) { blacklist = blacklist.filter(domain => domain !== host); this.setConfig('blacklist', blacklist); this.showNotification('已将当前网站从黑名单中移除'); if (this.shouldApplyMode()) { this.applyMode(); } } else { blacklist.push(host); this.setConfig('blacklist', blacklist); this.showNotification('已将当前网站添加到黑名单'); this.cleanupAllStyles(); } this.refreshMenu(); }, // 清空黑名单 clearBlacklist() { if (confirm('确定要清空黑名单吗?这将移除所有在黑名单中的网站。')) { this.setConfig('blacklist', []); this.showNotification('已清空黑名单'); this.refreshMenu(); } }, // 清空强制启用列表 clearForcedList() { if (confirm('确定要清空强制启用列表吗?这将移除所有强制启用的网站。')) { this.setConfig('forcedEnableList', []); this.showNotification('已清空强制启用列表'); this.applyMode(); this.refreshMenu(); } }, // 应用模式(整合柔和护眼、原版深色、护眼暗色、豆沙绿、深暖色) applyMode() { if (!this.shouldApplyMode()) { this.cleanupAllStyles(); return; } if (this.getConfig('autoExclude') && this.shouldExcludeSite()) { this.cleanupAllStyles(); return; } let siteSpecific = this.getSiteSpecificMode(); let modeToApply = null; let greenOpacity = null; let warmOpacity = null; // 解析网站专用模式 if (siteSpecific) { if (typeof siteSpecific === 'string') { modeToApply = siteSpecific; } else if (typeof siteSpecific === 'object' && siteSpecific.mode) { modeToApply = siteSpecific.mode; if (modeToApply === 'green' && typeof siteSpecific.opacity === 'number') { greenOpacity = siteSpecific.opacity; } else if (modeToApply === 'warm' && typeof siteSpecific.opacity === 'number') { warmOpacity = siteSpecific.opacity; } } } if (modeToApply && modeToApply !== 'default') { switch(modeToApply) { case 'light': this.cleanupAllStyles(); break; case 'dark': this.applyDarkMode(); break; case 'filter': this.applyFilterMode(); break; case 'classic': this.applyClassicDarkMode(); break; case 'soft': this.applySoftMode(); break; case 'eyecare': this.applyEyeCareMode(); break; case 'green': this.applyGreenMode(); if (greenOpacity !== null) { this.updateGreenOpacity(greenOpacity); } break; case 'warm': this.applyWarmMode(); if (warmOpacity !== null) { this.updateWarmOpacity(warmOpacity); } break; } } else { let mode = this.getCurrentMode(); switch(mode) { case 'dark': this.applyDarkMode(); break; case 'classic': this.applyClassicDarkMode(); break; case 'soft': this.applySoftMode(); break; case 'eyecare': this.applyEyeCareMode(); break; case 'filter': this.applyFilterMode(); break; case 'green': this.applyGreenMode(); break; case 'warm': this.applyWarmMode(); break; case 'light': default: this.cleanupAllStyles(); break; } } }, // 切换模式(根据用户配置的全局循环模式列表) switchMode() { let siteSpecific = this.getSiteSpecificMode(); if (siteSpecific && ((typeof siteSpecific === 'string' && siteSpecific !== 'default') || (typeof siteSpecific === 'object' && siteSpecific.mode !== 'default'))) { this.showNotification('当前网站使用专用模式,请在设置面板中修改'); setTimeout(() => this.showSettings(), 100); return; } let oldMode = this.currentMode || this.getConfig('currentMode') || 'light'; let cycleModes = this.getConfig('globalCycleModes'); // 确保是数组且过滤掉无效值 if (!Array.isArray(cycleModes)) cycleModes = ['dark']; const validModes = ['dark', 'classic', 'soft', 'eyecare', 'filter', 'green', 'warm']; cycleModes = cycleModes.filter(m => validModes.includes(m)); if (cycleModes.length === 0) cycleModes = ['dark']; let nextMode = 'light'; if (oldMode === 'light') { // 从白天切换到第一个循环模式 nextMode = cycleModes[0]; } else { // 在当前循环列表中找到下一个 let idx = cycleModes.indexOf(oldMode); if (idx !== -1 && idx < cycleModes.length - 1) { nextMode = cycleModes[idx + 1]; } else { // 已经是最后一个或未找到,回到白天 nextMode = 'light'; } } // 自动关闭专用模式逻辑(修复版) let autoMode = this.getConfig('autoDisableTempMode'); if (autoMode && autoMode !== '') { // 即将进入自动关闭模式 if (nextMode === autoMode && oldMode !== autoMode) { // 保存当前状态,并关闭专用总开关 this._prevTempDisableState = this.getConfig('tempDisableAllSiteModes'); if (!this._prevTempDisableState) { // 如果当前是开启状态(false),才需要关闭 this.setConfig('tempDisableAllSiteModes', true); this.showNotification(`已自动关闭专用模式(进入 ${this.getModeName(nextMode)} 模式)`); } else { // 已经是关闭状态,不重复操作 } } // 即将离开自动关闭模式 else if (oldMode === autoMode && nextMode !== autoMode && this._prevTempDisableState !== null) { // 恢复之前的状态 this.setConfig('tempDisableAllSiteModes', this._prevTempDisableState); this.showNotification(`已恢复专用模式(离开 ${this.getModeName(oldMode)} 模式)`); this._prevTempDisableState = null; } } this.currentMode = nextMode; this.setConfig('currentMode', nextMode); this.showNotification(`正在切换到 ${this.getModeName(nextMode)}`); setTimeout(() => { this.applyMode(); this.refreshMenu(); this.showNotification(`已切换到 ${this.getModeName(nextMode)}`); }, 100); }, // 获取模式名称(无图标) getModeName(mode) { switch(mode) { case 'light': return '白天模式'; case 'dark': return '夜间模式'; case 'classic': return '原版深色'; case 'soft': return '柔和护眼'; case 'eyecare': return '护眼暗色'; case 'filter': return '滤镜模式'; case 'green': return '豆沙绿'; case 'warm': return '深暖色'; default: return '白天模式'; } }, // 获取网站模式名称(带图标) getSiteModeName(mode) { if (typeof mode === 'object' && mode.mode === 'green') { return '🌱 豆沙绿'; } if (typeof mode === 'object' && mode.mode === 'warm') { return '🧡 深暖色'; } switch(mode) { case 'light': return '☀️ 白天模式'; case 'dark': return '🌙 夜间模式'; case 'filter': return '🎨 滤镜模式'; case 'classic': return '⚫ 原版深色'; case 'soft': return '🌿 柔和护眼'; case 'eyecare': return '👁️ 护眼暗色'; case 'green': return '🌱 豆沙绿'; case 'warm': return '🧡 深暖色'; default: return '默认'; } }, // 切换全局开关 toggleGlobal() { let current = this.getConfig('globalEnable'); this.setConfig('globalEnable', !current); this.applyMode(); this.refreshMenu(); this.showNotification(!current ? '已开启全局模式' : '已关闭全局模式'); }, // 切换当前网站开关(白名单管理) toggleCurrentSite() { let enableList = this.getConfig('enableList'); let host = location.host; if (enableList.includes(host)) { enableList = enableList.filter(domain => domain !== host); this.cleanupAllStyles(); this.setConfig('enableList', enableList); this.showNotification('已在当前网站禁用护眼模式'); } else { enableList.push(host); this.setConfig('enableList', enableList); this.applyMode(); this.showNotification('已在当前网站启用护眼模式'); } this.refreshMenu(); }, // 切换强制启用 toggleForceEnable() { let forcedList = this.getConfig('forcedEnableList'); let host = location.host; if (forcedList.includes(host)) { forcedList = forcedList.filter(domain => domain !== host); this.showNotification('已取消强制启用当前网站'); } else { forcedList.push(host); this.showNotification('已强制启用当前网站'); } this.setConfig('forcedEnableList', forcedList); this.applyMode(); this.refreshMenu(); }, // 切换全局暂时关闭所有专用模式 toggleTempDisableAllSiteModes() { let current = this.getConfig('tempDisableAllSiteModes'); let newState = !current; this.setConfig('tempDisableAllSiteModes', newState); // 如果当前处于自动关闭模式,且用户手动切换了状态,则更新保存的状态变量 let autoMode = this.getConfig('autoDisableTempMode'); if (autoMode && this.currentMode === autoMode && this._prevTempDisableState !== null) { this._prevTempDisableState = newState; } this.applyMode(); this.refreshMenu(); this.showNotification(newState ? '已关闭专用模式,使用全局设置' : '已恢复专用模式'); }, // 显示通知 showNotification(message) { let oldNotifications = document.querySelectorAll('.eye-protect-notification'); oldNotifications.forEach(notification => { notification.remove(); }); let notification = document.createElement('div'); notification.className = 'eye-protect-notification'; notification.style.cssText = ` position: fixed; top: 20px; right: 20px; background: rgba(0, 0, 0, 0.8); color: white; padding: 10px 20px; border-radius: 5px; z-index: 999999; font-size: 14px; box-shadow: 0 2px 5px rgba(0,0,0,0.2); opacity: 1; transition: opacity 0.5s; max-width: 300px; word-wrap: break-word; `; notification.textContent = message; if (document.body) { document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => notification.remove(), 500); }, 2000); } }, // 显示设置面板(优化响应速度,全局设置开关实时生效,强化样式隔离) showSettings() { // 确保document.body存在,否则延迟执行 if (!document.body) { setTimeout(() => this.showSettings(), 50); return; } try { // 移除现有的设置面板 let existingPanel = document.querySelector('.eye-protect-settings-panel'); if (existingPanel) { existingPanel.remove(); } // 立即创建并显示加载提示面板 let loadingPanel = document.createElement('div'); loadingPanel.className = 'eye-protect-settings-panel loading'; loadingPanel.style.cssText = ` position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 300px !important; background: #ffffff !important; color: #333333 !important; border-radius: 10px !important; box-shadow: 0 5px 20px rgba(0,0,0,0.2) !important; z-index: 2147483647 !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; border: 1px solid #e0e0e0 !important; padding: 20px !important; text-align: center !important; font-size: 14px !important; `; loadingPanel.innerHTML = `
⏳ 正在加载设置面板...
`; document.body.appendChild(loadingPanel); // 异步构建完整面板 setTimeout(() => { // 获取当前配置(此时可以安全读取) let currentMode = this.getCurrentMode(); let globalEnable = this.getConfig('globalEnable'); let enableList = this.getConfig('enableList'); let blacklist = this.getConfig('blacklist'); let autoExclude = this.getConfig('autoExclude'); let forcedList = this.getConfig('forcedEnableList'); let runDuringDay = this.getConfig('runDuringDay'); let darkAuto = this.getConfig('darkAuto'); let autoSwitch = this.getConfig('autoSwitch'); let customDayNight = this.getConfig('customDayNight'); let customDark3 = this.getConfig('customDark3'); let cycleModes = this.getConfig('globalCycleModes'); if (!Array.isArray(cycleModes)) cycleModes = ['dark']; const validModes = ['dark', 'classic', 'soft', 'eyecare', 'filter', 'green', 'warm']; cycleModes = cycleModes.filter(m => validModes.includes(m)); if (cycleModes.length === 0) cycleModes = ['dark']; let host = location.host; let siteEnabled = enableList.includes(host); let isForced = forcedList.includes(host); let isBlacklisted = blacklist.includes(host); let blacklistCount = blacklist.length; // 获取当前网站的专用模式及全局临时关闭状态 let siteSpecific = this.getSiteSpecificMode(); let siteSpecificMode = siteSpecific ? (typeof siteSpecific === 'string' ? siteSpecific : siteSpecific.mode) : null; let tempDisableAll = this.getConfig('tempDisableAllSiteModes'); let greenOpacityValue = this.getGreenOpacityForSite(); let warmOpacityValue = this.getWarmOpacityForSite(); let globalGreenOpacity = this.getConfig('globalGreenOpacity') || 0.3; let globalWarmOpacity = this.getConfig('globalWarmOpacity') || 0.3; let autoDisableTempMode = this.getConfig('autoDisableTempMode') || ''; // 计算实际生效的模式(用于显示) let effectiveMode = (siteSpecificMode && siteSpecificMode !== 'default' && !tempDisableAll) ? siteSpecificMode : currentMode; // 构建循环模式列表的HTML let buildCycleListHtml = () => { if (cycleModes.length === 0) { return '
暂无模式,请点击下方按钮添加
'; } let itemsHtml = ''; cycleModes.forEach((mode, idx) => { let name = ''; if (mode === 'dark') name = '🌙 夜间模式'; else if (mode === 'classic') name = '⚫ 原版深色'; else if (mode === 'soft') name = '🌿 柔和护眼'; else if (mode === 'eyecare') name = '👁️ 护眼暗色'; else if (mode === 'filter') name = '🎨 滤镜模式'; else if (mode === 'green') name = '🌱 豆沙绿'; else if (mode === 'warm') name = '🧡 深暖色'; itemsHtml += `
${name}
`; }); return itemsHtml; }; // 所有可用模式(除了白天模式) const availableModes = [ { id: 'dark', name: '🌙 夜间模式' }, { id: 'classic', name: '⚫ 原版深色' }, { id: 'soft', name: '🌿 柔和护眼' }, { id: 'eyecare', name: '👁️ 护眼暗色' }, { id: 'filter', name: '🎨 滤镜模式' }, { id: 'green', name: '🌱 豆沙绿' }, { id: 'warm', name: '🧡 深暖色' } ]; // 未添加的模式 let notAddedModes = availableModes.filter(m => !cycleModes.includes(m.id)); let addOptionsHtml = notAddedModes.map(m => ``).join(''); if (addOptionsHtml === '') addOptionsHtml = ''; // 自动关闭专用模式的模式选项 const autoModeOptions = [ { id: '', name: '不启用' }, { id: 'dark', name: '🌙 夜间模式' }, { id: 'classic', name: '⚫ 原版深色' }, { id: 'soft', name: '🌿 柔和护眼' }, { id: 'eyecare', name: '👁️ 护眼暗色' }, { id: 'filter', name: '🎨 滤镜模式' }, { id: 'green', name: '🌱 豆沙绿' }, { id: 'warm', name: '🧡 深暖色' } ]; let autoModeOptionsHtml = autoModeOptions.map(opt => ``).join(''); // 创建设置面板(完全隔离样式) let panel = document.createElement('div'); panel.className = 'eye-protect-settings-panel'; // 强制重置所有可能影响布局的样式 panel.setAttribute('style', ` all: initial !important; position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 600px !important; max-width: 95% !important; max-height: 85vh !important; background: #ffffff !important; color: #333333 !important; border-radius: 10px !important; box-shadow: 0 5px 20px rgba(0,0,0,0.2) !important; z-index: 2147483647 !important; overflow: hidden !important; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; border: 1px solid #e0e0e0 !important; font-size: 14px !important; line-height: 1.4 !important; box-sizing: border-box !important; `); // 内部内容使用严格的内联样式 panel.innerHTML = `
🛡️ 夜间护眼助手设置 v5.21.11

📱 当前状态

当前模式: ${this.getSiteModeName(effectiveMode)} ${tempDisableAll && siteSpecificMode ? '(专用模式已关闭)' : (siteSpecificMode ? '[专用]' : '')}
当前网站: ${host}
全局开关: ${globalEnable ? '✅ 已开启' : '❌ 已关闭'}
白名单状态: ${siteEnabled ? '✅ 已启用' : '❌ 未启用'}
黑名单状态: ${isBlacklisted ? '⛔ 已禁用' : '❌ 未禁用'}
强制启用: ${isForced ? '⚡ 已强制启用' : '🚫 未强制启用'}
网站专用模式: ${siteSpecificMode ? this.getSiteModeName(siteSpecificMode) : '默认'} ${tempDisableAll ? '(已关闭)' : ''}

🌐 网站专用模式设置

输入域名或完整网址(如 google.com 或 https://www.google.com),然后选择模式,即可为该网站设置专用模式
当前输入域名的专用模式:${(() => { let inputDomain = document.getElementById('siteDomain')?.value; if (!inputDomain) return '默认'; let hostname = this.extractHostname(inputDomain); let mode = this.getSiteSpecificMode(hostname); return this.getSiteModeName(mode); })()}
当前浓度:${Math.round(greenOpacityValue * 100)}%
当前浓度:${Math.round(warmOpacityValue * 100)}%
${tempDisableAll ? '已关闭' : '已启用'}
📖 模式说明

🌿 全局豆沙绿浓度

此浓度仅在全局模式为豆沙绿且没有网站专用模式覆盖时生效。
当前浓度:${Math.round(globalGreenOpacity * 100)}%

🧡 全局深暖色浓度

此浓度仅在全局模式为深暖色且没有网站专用模式覆盖时生效。
当前浓度:${Math.round(globalWarmOpacity * 100)}%

⚡ 强制启用管理

新规则:强制启用列表中的网站需要满足以下条件之一才会生效:
1. 全局开关开启
2. 该网站在白名单中
注意:强制启用仍然不受黑名单影响
强制启用状态:
${host}:${isForced ? '已强制启用' : '未强制启用'}
当前生效条件:
• 全局开关:${globalEnable ? '✅ 已开启' : '❌ 已关闭'}
• 白名单:${siteEnabled ? '✅ 已启用' : '❌ 未启用'}
强制启用生效: ${isForced && (globalEnable || siteEnabled) ? '✅ 是' : '❌ 否'}

⛔ 黑名单管理

黑名单中的网站将始终禁用护眼模式,优先级最高(强制启用也受黑名单影响)
当前黑名单状态:${blacklistCount} 个网站
${host}:${isBlacklisted ? '⛔ 已禁用' : '❌ 未禁用'}
优先级顺序:
1. 黑名单(最高优先级)
2. 强制启用(需要全局或白名单开启)
3. 全局开关
4. 白名单
${blacklistCount > 0 ? `
当前黑名单网站:
${blacklist.map(site => `
${site}
`).join('')}
` : ''}

🔄 全局循环模式配置

自定义全局切换时循环的模式顺序(白天模式固定为起点和终点)。
${buildCycleListHtml()}
提示:选择某个模式后,切换到该模式时会自动关闭“全局专用总开关”,离开时恢复之前状态。

⚙️ 全局设置(实时生效)

全局开关:
智能排除:
白天开启:
跟随系统:

🕐 时间设置

格式:白天开始时间|白天结束时间,如6:00|18:00
格式:白天模式|夜间模式,0=白天 1=夜间
夜间模式的强度,默认90(0-100)
`; // 移除加载面板,添加完整面板 loadingPanel.remove(); document.body.appendChild(panel); // 添加全局样式确保所有元素不受外部影响 let globalResetStyle = document.createElement('style'); globalResetStyle.textContent = ` .eye-protect-settings-panel, .eye-protect-settings-panel * { all: initial; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important; box-sizing: border-box !important; } .eye-protect-settings-panel { position: fixed !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: 600px !important; max-width: 95% !important; max-height: 85vh !important; background: #ffffff !important; color: #333333 !important; border-radius: 10px !important; box-shadow: 0 5px 20px rgba(0,0,0,0.2) !important; z-index: 2147483647 !important; overflow: hidden !important; border: 1px solid #e0e0e0 !important; font-size: 14px !important; line-height: 1.4 !important; } .eye-protect-settings-panel .settings-content { padding: 20px !important; max-height: calc(85vh - 70px) !important; overflow-y: auto !important; overflow-x: hidden !important; background: #ffffff !important; color: #333333 !important; font-size: 14px !important; line-height: 1.4 !important; scrollbar-width: none !important; -ms-overflow-style: none !important; } .eye-protect-settings-panel .settings-content::-webkit-scrollbar { display: none !important; } .eye-protect-settings-panel .toggle-switch input:checked + .toggle-slider::before { transform: translateX(26px) !important; } .eye-protect-settings-panel .toggle-switch input:checked + .toggle-slider { background-color: #007bff !important; } .eye-protect-settings-panel .toggle-switch .toggle-slider::before { content: "" !important; position: absolute !important; height: 16px !important; width: 16px !important; left: 4px !important; bottom: 4px !important; background-color: white !important; border-radius: 50% !important; transition: .4s !important; } .eye-protect-settings-panel .temp-disable-toggle .toggle-slider { background-color: #007bff !important; } .eye-protect-settings-panel .temp-disable-toggle input:checked + .toggle-slider { background-color: #dc3545 !important; } .eye-protect-settings-panel input[type="range"] { -webkit-appearance: auto !important; appearance: auto !important; background: #f0f0f0 !important; height: 4px !important; border-radius: 2px !important; } .eye-protect-settings-panel input[type="range"]::-webkit-slider-thumb { -webkit-appearance: auto !important; appearance: auto !important; width: 16px !important; height: 16px !important; border-radius: 50% !important; background: #007bff !important; cursor: pointer !important; } .eye-protect-settings-panel input[type="range"]::-moz-range-thumb { width: 16px !important; height: 16px !important; border-radius: 50% !important; background: #007bff !important; cursor: pointer !important; border: none !important; } `; document.head.appendChild(globalResetStyle); // 事件绑定 setTimeout(() => { // 关闭按钮事件 panel.querySelector('#closeBtn').addEventListener('click', (e) => { e.stopPropagation(); panel.remove(); }); // ========== 全局设置开关实时生效 ========== const globalEnableCheckbox = panel.querySelector('#globalEnable'); const autoExcludeCheckbox = panel.querySelector('#autoExclude'); const runDuringDayCheckbox = panel.querySelector('#runDuringDay'); const darkAutoCheckbox = panel.querySelector('#darkAuto'); const applyGlobalSetting = () => { this.applyMode(); this.refreshMenu(); }; if (globalEnableCheckbox) { globalEnableCheckbox.addEventListener('change', (e) => { this.setConfig('globalEnable', e.target.checked); applyGlobalSetting(); }); } if (autoExcludeCheckbox) { autoExcludeCheckbox.addEventListener('change', (e) => { this.setConfig('autoExclude', e.target.checked); applyGlobalSetting(); }); } if (runDuringDayCheckbox) { runDuringDayCheckbox.addEventListener('change', (e) => { this.setConfig('runDuringDay', e.target.checked); this.applyMode(); }); } if (darkAutoCheckbox) { darkAutoCheckbox.addEventListener('change', (e) => { this.setConfig('darkAuto', e.target.checked); this.applyMode(); }); } // 自动关闭专用模式的选择框 const autoDisableSelect = panel.querySelector('#autoDisableTempModeSelect'); if (autoDisableSelect) { autoDisableSelect.addEventListener('change', (e) => { let selected = e.target.value; this.setConfig('autoDisableTempMode', selected); // 如果当前模式正好是选中的模式,需要立即应用一次(避免下次切换才生效) if (selected && this.currentMode === selected) { // 进入特殊模式逻辑:保存当前状态并关闭专用总开关 let currentTempState = this.getConfig('tempDisableAllSiteModes'); this._prevTempDisableState = currentTempState; if (!currentTempState) { this.setConfig('tempDisableAllSiteModes', true); this.applyMode(); this.refreshMenu(); this.showNotification(`已自动关闭专用模式(进入 ${this.getModeName(this.currentMode)} 模式)`); } } else if (!selected && this.currentMode === this.getConfig('autoDisableTempMode')) { // 原来选中现在取消,且当前在之前选中的模式中,需要恢复状态 if (this._prevTempDisableState !== null) { this.setConfig('tempDisableAllSiteModes', this._prevTempDisableState); this.applyMode(); this.refreshMenu(); this.showNotification(`已恢复专用模式(离开 ${this.getModeName(this.currentMode)} 模式)`); this._prevTempDisableState = null; } } }); } // 网站专用模式相关事件 const siteDomainInput = panel.querySelector('#siteDomain'); const siteModeSelect = panel.querySelector('#siteSpecificMode'); const greenOpacityContainer = panel.querySelector('#greenOpacityContainer'); const greenOpacitySlider = panel.querySelector('#greenOpacity'); const greenOpacityValueSpan = panel.querySelector('#greenOpacityValue'); const warmOpacityContainer = panel.querySelector('#warmOpacityContainer'); const warmOpacitySlider = panel.querySelector('#warmOpacity'); const warmOpacityValueSpan = panel.querySelector('#warmOpacityValue'); const tempDisableAllCheckbox = panel.querySelector('#tempDisableAllSiteModes'); // 监听模式选择变化,显示/隐藏浓度调节区 const toggleOpacityContainers = () => { const selected = siteModeSelect.value; if (selected === 'green') { greenOpacityContainer.style.display = 'block'; warmOpacityContainer.style.display = 'none'; } else if (selected === 'warm') { greenOpacityContainer.style.display = 'none'; warmOpacityContainer.style.display = 'block'; } else { greenOpacityContainer.style.display = 'none'; warmOpacityContainer.style.display = 'none'; } }; siteModeSelect.addEventListener('change', toggleOpacityContainers); toggleOpacityContainers(); // 浓度滑块事件 if (greenOpacitySlider) { greenOpacitySlider.addEventListener('input', (e) => { let val = parseFloat(e.target.value); greenOpacityValueSpan.textContent = Math.round(val * 100); }); } if (warmOpacitySlider) { warmOpacitySlider.addEventListener('input', (e) => { let val = parseFloat(e.target.value); warmOpacityValueSpan.textContent = Math.round(val * 100); }); } // 全局专用总开关 if (tempDisableAllCheckbox) { tempDisableAllCheckbox.addEventListener('change', (e) => { let isChecked = e.target.checked; this.setConfig('tempDisableAllSiteModes', isChecked); // 如果当前处于自动关闭模式,则更新保存的状态 let autoMode = this.getConfig('autoDisableTempMode'); if (autoMode && this.currentMode === autoMode && this._prevTempDisableState !== null) { this._prevTempDisableState = isChecked; } this.applyMode(); this.refreshMenu(); // 更新开关背景色 const slider = tempDisableAllCheckbox.closest('.toggle-switch').querySelector('.toggle-slider'); if (slider) { slider.style.backgroundColor = isChecked ? '#dc3545' : '#007bff'; } // 更新状态文字颜色 const statusSpan = tempDisableAllCheckbox.closest('div[style*="display: flex"]')?.querySelector('span[style*="font-size: 12px"]'); if (statusSpan) { statusSpan.style.color = isChecked ? '#dc3545' : '#28a745'; statusSpan.textContent = isChecked ? '已关闭' : '已启用'; } this.showNotification(isChecked ? '已关闭专用模式,使用全局设置' : '已恢复专用模式'); }); // 初始化开关样式 const slider = tempDisableAllCheckbox.closest('.toggle-switch').querySelector('.toggle-slider'); if (slider) { slider.style.backgroundColor = tempDisableAll ? '#dc3545' : '#007bff'; } } panel.querySelector('#applySiteMode').addEventListener('click', () => { let rawInput = siteDomainInput ? siteDomainInput.value.trim() : ''; if (!rawInput) { alert('请输入域名或网址'); return; } let hostname = this.extractHostname(rawInput); if (!hostname) { alert('无法解析域名,请检查输入格式'); return; } let mode = siteModeSelect.value; if (mode === 'default') { this.setSiteSpecificMode(hostname, null); this.showNotification(`已重置 ${hostname} 的专用模式为默认`); } else if (mode === 'green') { let opacity = greenOpacitySlider ? parseFloat(greenOpacitySlider.value) : 0.3; this.setSiteSpecificMode(hostname, 'green', opacity); this.showNotification(`已为 ${hostname} 设置专用模式:豆沙绿,浓度 ${Math.round(opacity * 100)}%`); } else if (mode === 'warm') { let opacity = warmOpacitySlider ? parseFloat(warmOpacitySlider.value) : 0.3; this.setSiteSpecificMode(hostname, 'warm', opacity); this.showNotification(`已为 ${hostname} 设置专用模式:深暖色,浓度 ${Math.round(opacity * 100)}%`); } else { this.setSiteSpecificMode(hostname, mode); this.showNotification(`已为 ${hostname} 设置专用模式:${this.getSiteModeName(mode)}`); } if (hostname === location.host) { this.applyMode(); this.refreshMenu(); } panel.remove(); }); panel.querySelector('#resetSiteMode').addEventListener('click', () => { let rawInput = siteDomainInput ? siteDomainInput.value.trim() : ''; if (!rawInput) { alert('请输入域名或网址'); return; } let hostname = this.extractHostname(rawInput); if (!hostname) { alert('无法解析域名,请检查输入格式'); return; } this.setSiteSpecificMode(hostname, null); this.showNotification(`已重置 ${hostname} 的专用模式为默认`); if (hostname === location.host) { this.applyMode(); this.refreshMenu(); } panel.remove(); }); // 模式说明折叠/展开功能 let descDiv = panel.querySelector('.mode-description'); if (descDiv) { let descContent = descDiv.querySelector('.desc-content'); let toggleSpan = descDiv.querySelector('.desc-toggle'); const headerDiv = descDiv.querySelector('div:first-child'); if (headerDiv) { headerDiv.addEventListener('click', (e) => { e.stopPropagation(); if (descContent.style.display === 'none') { descContent.style.display = 'block'; toggleSpan.textContent = '▲'; } else { descContent.style.display = 'none'; toggleSpan.textContent = '▼'; } }); } } // 全局循环模式列表的动态更新函数 let cycleListDiv = panel.querySelector('#cycleList'); let addModeSelect = panel.querySelector('#addModeSelect'); let addModeBtn = panel.querySelector('#addModeBtn'); const updateCycleListDisplay = () => { let currentModes = this.getConfig('globalCycleModes'); if (!Array.isArray(currentModes)) currentModes = ['dark']; const valid = ['dark', 'classic', 'soft', 'eyecare', 'filter', 'green', 'warm']; currentModes = currentModes.filter(m => valid.includes(m)); if (currentModes.length === 0) currentModes = ['dark']; // 更新存储 this.setConfig('globalCycleModes', currentModes); // 更新显示 let html = ''; currentModes.forEach((mode, idx) => { let name = ''; if (mode === 'dark') name = '🌙 夜间模式'; else if (mode === 'classic') name = '⚫ 原版深色'; else if (mode === 'soft') name = '🌿 柔和护眼'; else if (mode === 'eyecare') name = '👁️ 护眼暗色'; else if (mode === 'filter') name = '🎨 滤镜模式'; else if (mode === 'green') name = '🌱 豆沙绿'; else if (mode === 'warm') name = '🧡 深暖色'; html += `
${name}
`; }); cycleListDiv.innerHTML = html; // 绑定上下移动和删除事件 cycleListDiv.querySelectorAll('.cycle-up').forEach(btn => { btn.addEventListener('click', () => { let item = btn.closest('.cycle-item'); let idx = parseInt(item.dataset.idx); if (idx > 0) { let modes = this.getConfig('globalCycleModes'); if (!Array.isArray(modes)) modes = ['dark']; modes = modes.filter(m => valid.includes(m)); if (modes.length === 0) modes = ['dark']; let temp = modes[idx]; modes[idx] = modes[idx-1]; modes[idx-1] = temp; this.setConfig('globalCycleModes', modes); updateCycleListDisplay(); } }); }); cycleListDiv.querySelectorAll('.cycle-down').forEach(btn => { btn.addEventListener('click', () => { let item = btn.closest('.cycle-item'); let idx = parseInt(item.dataset.idx); let modes = this.getConfig('globalCycleModes'); if (!Array.isArray(modes)) modes = ['dark']; modes = modes.filter(m => valid.includes(m)); if (modes.length === 0) modes = ['dark']; if (idx < modes.length - 1) { let temp = modes[idx]; modes[idx] = modes[idx+1]; modes[idx+1] = temp; this.setConfig('globalCycleModes', modes); updateCycleListDisplay(); } }); }); cycleListDiv.querySelectorAll('.cycle-remove').forEach(btn => { btn.addEventListener('click', () => { let item = btn.closest('.cycle-item'); let idx = parseInt(item.dataset.idx); let modes = this.getConfig('globalCycleModes'); if (!Array.isArray(modes)) modes = ['dark']; modes = modes.filter(m => valid.includes(m)); if (modes.length === 1) { alert('至少保留一个模式,无法删除'); return; } modes.splice(idx, 1); this.setConfig('globalCycleModes', modes); updateCycleListDisplay(); // 刷新添加模式下拉框 refreshAddSelect(); }); }); // 刷新添加模式下拉框 refreshAddSelect(); }; const refreshAddSelect = () => { let currentModes = this.getConfig('globalCycleModes'); if (!Array.isArray(currentModes)) currentModes = ['dark']; const valid = ['dark', 'classic', 'soft', 'eyecare', 'filter', 'green', 'warm']; currentModes = currentModes.filter(m => valid.includes(m)); const available = valid.filter(m => !currentModes.includes(m)); let optionsHtml = available.map(m => { let name = ''; if (m === 'dark') name = '🌙 夜间模式'; else if (m === 'classic') name = '⚫ 原版深色'; else if (m === 'soft') name = '🌿 柔和护眼'; else if (m === 'eyecare') name = '👁️ 护眼暗色'; else if (m === 'filter') name = '🎨 滤镜模式'; else if (m === 'green') name = '🌱 豆沙绿'; else if (m === 'warm') name = '🧡 深暖色'; return ``; }).join(''); if (optionsHtml === '') optionsHtml = ''; addModeSelect.innerHTML = optionsHtml; }; if (addModeBtn) { addModeBtn.addEventListener('click', () => { let selectedMode = addModeSelect.value; if (!selectedMode || selectedMode === 'disabled') return; let currentModes = this.getConfig('globalCycleModes'); if (!Array.isArray(currentModes)) currentModes = ['dark']; const valid = ['dark', 'classic', 'soft', 'eyecare', 'filter', 'green', 'warm']; currentModes = currentModes.filter(m => valid.includes(m)); if (currentModes.includes(selectedMode)) { alert('该模式已在列表中'); return; } currentModes.push(selectedMode); this.setConfig('globalCycleModes', currentModes); updateCycleListDisplay(); }); } // 初始化循环列表 updateCycleListDisplay(); // 全局豆沙绿浓度调节 const globalGreenSlider = panel.querySelector('#globalGreenOpacitySlider'); const globalGreenValueSpan = panel.querySelector('#globalGreenOpacityValue'); if (globalGreenSlider) { globalGreenSlider.addEventListener('input', (e) => { let val = parseFloat(e.target.value); globalGreenValueSpan.textContent = Math.round(val * 100); }); globalGreenSlider.addEventListener('change', (e) => { let val = parseFloat(e.target.value); this.updateGlobalGreenOpacity(val); this.showNotification(`全局豆沙绿浓度已调整为 ${Math.round(val * 100)}%`); }); } // 全局深暖色浓度调节 const globalWarmSlider = panel.querySelector('#globalWarmOpacitySlider'); const globalWarmValueSpan = panel.querySelector('#globalWarmOpacityValue'); if (globalWarmSlider) { globalWarmSlider.addEventListener('input', (e) => { let val = parseFloat(e.target.value); globalWarmValueSpan.textContent = Math.round(val * 100); }); globalWarmSlider.addEventListener('change', (e) => { let val = parseFloat(e.target.value); this.updateGlobalWarmOpacity(val); this.showNotification(`全局深暖色浓度已调整为 ${Math.round(val * 100)}%`); }); } // 强制启用相关事件 panel.querySelector('#toggleForceEnable').addEventListener('click', () => { this.toggleForceEnable(); panel.remove(); }); panel.querySelector('#clearForcedList').addEventListener('click', () => { this.clearForcedList(); }); // 黑名单相关事件 panel.querySelector('#toggleBlacklist').addEventListener('click', () => { this.toggleBlacklist(); panel.remove(); }); panel.querySelector('#clearBlacklist').addEventListener('click', () => { this.clearBlacklist(); }); // 功能按钮事件 panel.querySelector('#toggleMode').addEventListener('click', () => { this.switchMode(); panel.remove(); }); panel.querySelector('#toggleSite').addEventListener('click', () => { this.toggleCurrentSite(); panel.remove(); }); panel.querySelector('#saveSettings').addEventListener('click', () => { this.setConfig('globalEnable', panel.querySelector('#globalEnable').checked); this.setConfig('autoExclude', panel.querySelector('#autoExclude').checked); this.setConfig('runDuringDay', panel.querySelector('#runDuringDay').checked); this.setConfig('darkAuto', panel.querySelector('#darkAuto').checked); let dayNight = panel.querySelector('#customDayNight').value; if (dayNight) this.setConfig('customDayNight', dayNight); let autoSwitchVal = panel.querySelector('#autoSwitch').value; this.setConfig('autoSwitch', autoSwitchVal); let dark3Val = panel.querySelector('#customDark3').value; if (dark3Val) this.setConfig('customDark3', dark3Val); this.applyMode(); this.refreshMenu(); this.showNotification('✅ 设置已保存'); panel.remove(); }); panel.querySelector('#resetSettings').addEventListener('click', () => { if (confirm('确定要恢复所有默认设置吗?')) { for (let key in this.defaults) { this.setConfig(key, this.defaults[key]); } this.setConfig('currentMode', 'light'); this.currentMode = 'light'; this._prevTempDisableState = null; this.applyMode(); this.refreshMenu(); this.showNotification('✅ 已恢复默认设置'); panel.remove(); } }); // ESC键关闭 const handleEscKey = (e) => { if (e.key === 'Escape' && panel && panel.parentNode) { panel.remove(); document.removeEventListener('keydown', handleEscKey); } }; document.addEventListener('keydown', handleEscKey); }, 0); }, 0); } catch (e) { console.error('设置面板打开失败:', e); alert('设置面板打开失败,请查看控制台错误信息。'); } }, // 清除所有菜单 clearMenu() { menuCommands.forEach(cmd => { try { GM_unregisterMenuCommand(cmd); } catch (e) { // 忽略错误 } }); menuCommands = []; }, // 刷新菜单 refreshMenu() { this.clearMenu(); this.initMenu(); }, // 初始化菜单 initMenu() { let currentMode = this.getCurrentMode(); let globalEnable = this.getConfig('globalEnable'); let enableList = this.getConfig('enableList'); let host = location.host; let siteEnabled = enableList.includes(host); let siteSpecific = this.getSiteSpecificMode(); let siteSpecificMode = siteSpecific ? (typeof siteSpecific === 'string' ? siteSpecific : siteSpecific.mode) : null; // 确定当前生效的模式(专用模式优先,但考虑全局临时关闭) let effectiveMode = (siteSpecificMode && siteSpecificMode !== 'default' && !this.getConfig('tempDisableAllSiteModes')) ? siteSpecificMode : currentMode; let modeDisplay = this.getSiteModeName(effectiveMode); if (siteSpecificMode && siteSpecificMode !== 'default') { modeDisplay += ' [专用]'; if (this.getConfig('tempDisableAllSiteModes')) { modeDisplay += ' (已关闭)'; } } menuCommands.push(GM_registerMenuCommand(modeDisplay, () => { this.switchMode(); })); menuCommands.push(GM_registerMenuCommand(globalEnable ? '🌍 全局: 开启 (点击关闭)' : '🌍 全局: 关闭 (点击开启)', () => { this.toggleGlobal(); })); menuCommands.push(GM_registerMenuCommand(siteEnabled ? '✅ 本站: 启用 (点击禁用)' : '❌ 本站: 禁用 (点击启用)', () => { this.toggleCurrentSite(); })); menuCommands.push(GM_registerMenuCommand('⚙️ 设置面板', () => { this.showSettings(); })); } }; // 立即初始化 EyeProtect.init(); })();