`;
document.body.appendChild(notificationElement);
timeout = setTimeout(() => {
if (notificationElement) {
notificationElement.style.animation = 'fadeOut 0.3s ease-out forwards';
setTimeout(cleanup, 300);
}
}, duration);
} catch(e) {}
};
fn.cancel = cleanup;
return fn;
})(),
setCache(key, value, ttl = CONFIG.CACHE_TTL) { cache.set(key, new CacheItem(value, ttl)); },
getCache(key) { const item = cache.get(key); if (item && item.isValid()) return item.value; if (item) cache.delete(key); return null; },
clearCache() { cache.clear(); },
startCleanup,
stopCleanup,
migrateFlickerList,
detectPageBrightness,
dispose() {
stopCleanup();
this.clearCache();
this.removeElementById(CONFIG.STYLE_ID);
this.removeElementById(CONFIG.ANIMATION_ID);
this.removeElementById(CONFIG.FILTER_STYLE_ID);
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
greenOverlay = warmOverlay = null;
if (styleIntegrityTimer) clearInterval(styleIntegrityTimer);
if (recoveryObserver) recoveryObserver.disconnect();
if (headObserver) headObserver.disconnect();
if (preloadTimer) clearTimeout(preloadTimer);
}
};
})();
Util.addStyle(CONFIG.ANIMATION_ID, `
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes fadeOut { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
`);
Util.migrateFlickerList();
// ==================== 强制专用模式相关函数 ====================
function isForceSpecialSite() {
if (!Util.getValue('forceSpecialModeEnabled')) return false;
const host = location.host;
const list = Util.getValue('forceSpecialList') || [];
for (const item of list) {
let domain = typeof item === 'string' ? item : item.domain;
if (domain === host) return true;
}
return false;
}
function getForceSpecialModeForSite() {
if (!isForceSpecialSite()) return null;
const host = location.host;
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
let entry = siteModes[host];
if (!entry) return null;
if (typeof entry === 'string') {
if (VALID_MODES.includes(entry)) return entry;
return null;
}
if (typeof entry === 'object' && entry.mode && VALID_MODES.includes(entry.mode)) return entry.mode;
return null;
} catch(e) { return null; }
}
function addToForceSpecialList(domain) {
let list = Util.getValue('forceSpecialList') || [];
const exists = list.some(item => {
let d = typeof item === 'string' ? item : item.domain;
return d === domain;
});
if (exists) return false;
list.push({ domain: domain, addedTime: Date.now() });
Util.setValue('forceSpecialList', list);
Util.showNotification(`已将 ${domain} 添加到强制专用模式列表`, 'success', 1500);
return true;
}
function removeFromForceSpecialList(domain) {
let list = Util.getValue('forceSpecialList') || [];
let newList = list.filter(item => {
let d = typeof item === 'string' ? item : item.domain;
return d !== domain;
});
Util.setValue('forceSpecialList', newList);
Util.showNotification(`已从强制专用模式列表移除 ${domain}`, 'success', 1500);
}
function clearForceSpecialList() {
Util.setValue('forceSpecialList', []);
Util.showNotification('已清空强制专用模式列表', 'success', 1500);
}
// ==================== 样式生成函数 ====================
function generateFullStyleCSS(mode) {
if (mode === 'light') return '';
if (mode === 'dark') {
let style_30 = Util.getValue('customDark3') || '90';
let dark3Exclude = Util.getValue('dark3Exclude');
let isFirefox = /Firefox/i.test(navigator.userAgent);
let filterValue = `invert(${style_30}%)`;
let baseCSS = isFirefox ?
`html { filter: ${filterValue} !important; background-image: url(); text-shadow: 0 0 0 !important; }` :
`html { filter: ${filterValue} !important; text-shadow: 0 0 0 !important; }`;
let excludeCSS = `${dark3Exclude} { filter: invert(1) !important; }`;
let scrollbarCSS = `
::-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; }
`;
return baseCSS + excludeCSS + scrollbarCSS;
}
if (mode === 'filter') {
return '';
}
if (THEMES[mode]) {
const theme = THEMES[mode];
let inv = theme.invert;
let hue = theme.hue;
let bright = theme.bright;
let contrast = theme.contrast;
let saturate = theme.saturate;
let bg = theme.bg;
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})`;
let css = `html{filter:${filter}!important;background:${bg};will-change:filter,background;}`;
css += `img, video, iframe, canvas, [style*="background-image"]{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
css += `img[src*=".svg"], svg{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
css += `video::-webkit-media-controls-panel, video::-webkit-media-controls{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
css += `picture, source{filter:${mediaFilter}!important;-webkit-filter:${mediaFilter}!important;}`;
return css;
}
return '';
}
function generateCodePageCSS() {
let style_30 = Util.getValue('customDark3') || '90';
let isFirefox = /Firefox/i.test(navigator.userAgent);
let baseCSS = isFirefox ?
`html { filter: invert(${style_30}%) !important; background-image: url(); background-color: #0d1117 !important; text-shadow: 0 0 0 !important; }` :
`html { filter: invert(${style_30}%) !important; background-color: #0d1117 !important; text-shadow: 0 0 0 !important; }`;
let excludeCSS = `img, svg, canvas, iframe, .icon, .logo, .avatar, [src*=".svg"], [src*=".png"], [src*=".jpg"], [src*=".jpeg"], [src*=".gif"] { filter: invert(1) !important; }`;
let codeCSS = `
body, html { background-color: #0d1117 !important; color: #c9d1d9 !important; }
pre, code, .hljs, .CodeMirror, .code-viewer, .code-area, .editor-container,
.container, .main-container, .wrapper, .content, .code-container, .script-container,
.page-content, .main-content, div[style*="font-family:monospace"],
div[style*="font-family: monospace"] {
background-color: #0d1117 !important;
color: #c9d1d9 !important;
border-color: #30363d !important;
}
.header, .navbar, .nav, .top-bar, .breadcrumb, .nav-bar {
background-color: #161b22 !important;
border-color: #30363d !important;
color: #c9d1d9 !important;
}
.line-numbers, .line-number, .linenumber, .ln, .lnum {
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 {
color: #d2a8ff !important;
}
.string, [class*="key"] { color: #79c0ff !important; }
.number { color: #56d364 !important; }
.boolean, .null { color: #ff7b72 !important; }
::-webkit-scrollbar { background-color: #0d1117 !important; }
::-webkit-scrollbar-thumb { background-color: #30363d !important; border-radius: 6px; }
a { color: #58a6ff !important; }
button, .btn, .button, input[type="button"], input[type="submit"] {
background-color: #238636 !important;
color: #ffffff !important;
border-color: #2ea043 !important;
}
::selection { background-color: #1c6b48 !important; color: #ffffff !important; }
`;
return baseCSS + excludeCSS + codeCSS;
}
function isJsonOrCodePage() {
const url = location.href;
if (url.endsWith('.js') || url.includes('.js?') || url.endsWith('.user.js') ||
url.endsWith('.json') || url.includes('.json?') || url.includes('.json#')) {
return true;
}
if (url.includes('raw.githubusercontent.com') || url.includes('gh-proxy.org') ||
url.includes('scriptcat.org/code/') || url.includes('updategf.qytechs.cn/scripts/')) {
return true;
}
const contentType = document.contentType || '';
if (contentType.includes('application/json') || contentType.includes('text/json')) {
return true;
}
if (document.body) {
const text = document.body.textContent.trim();
if ((text.startsWith('{') && text.endsWith('}')) || (text.startsWith('[') && text.endsWith(']'))) {
try { JSON.parse(text); return true; } catch(e) {}
}
}
return false;
}
function getCSSForMode(mode) {
if (mode === 'light') return '';
let cacheKey = mode;
if (mode === 'dark') cacheKey = `dark_${Util.getValue('customDark3')}`;
if (cachedCSS.has(cacheKey)) return cachedCSS.get(cacheKey);
let css = '';
const isCodePage = isJsonOrCodePage();
if (isCodePage) {
css = generateCodePageCSS();
} else if (mode === 'dark') {
css = generateFullStyleCSS('dark');
} else if (mode === 'filter') {
css = null;
} else if (mode === 'green' || mode === 'warm') {
css = null;
} else if (THEMES[mode]) {
css = generateFullStyleCSS(mode);
}
if (css !== null) {
cachedCSS.set(cacheKey, css);
if (cachedCSS.size > 20) {
const firstKey = cachedCSS.keys().next().value;
cachedCSS.delete(firstKey);
}
}
return css;
}
function applyModeByMode(mode) {
if (mode === 'green' || mode === 'warm') {
removeFilterMode();
Util.removeElementById(CONFIG.STYLE_ID);
applyOverlay(mode);
return;
}
if (mode === 'filter') {
removeOverlays();
Util.removeElementById(CONFIG.STYLE_ID);
applyFilterMode();
return;
}
removeOverlays();
removeFilterMode();
const css = getCSSForMode(mode);
if (css === undefined || css === null || css === '') {
Util.removeElementById(CONFIG.STYLE_ID);
} else {
Util.addStyle(CONFIG.STYLE_ID, css);
}
}
function removeOverlays() {
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
greenOverlay = null; warmOverlay = null;
}
function applyOverlay(mode) {
// 对于覆盖层,确保附加到 document.documentElement 上,即使 body 未存在
const target = document.documentElement;
if (!target) return;
if (mode === 'green') {
let opacity = getGreenOpacityForSite();
if (!greenOverlay || !target.contains(greenOverlay)) {
if (greenOverlay && greenOverlay.parentNode) greenOverlay.remove();
greenOverlay = document.createElement('div');
greenOverlay.id = 'eye-protect-green-overlay';
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;background-color:#C7EDCC;opacity:${opacity};`;
target.appendChild(greenOverlay);
} else {
greenOverlay.style.opacity = opacity;
}
} else if (mode === 'warm') {
let opacity = getWarmOpacityForSite();
if (!warmOverlay || !target.contains(warmOverlay)) {
if (warmOverlay && warmOverlay.parentNode) warmOverlay.remove();
warmOverlay = document.createElement('div');
warmOverlay.id = 'eye-protect-warm-overlay';
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;background-color:#CFB988;opacity:${opacity};`;
target.appendChild(warmOverlay);
} else {
warmOverlay.style.opacity = opacity;
}
}
}
function removeFilterMode() {
if (filterStyle && filterStyle.parentNode) filterStyle.remove();
let svg = document.getElementById(CONFIG.FILTER_SVG_ID);
if (svg) svg.remove();
filterStyle = null;
}
function applyFilterMode() {
removeFilterMode();
let excludeList = Util.getValue('filterExcludeList');
if (excludeList.includes(location.host)) return;
if (!document.getElementById(CONFIG.FILTER_SVG_ID)) {
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);
}
let isFirefox = /Firefox/i.test(navigator.userAgent);
let filter = 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 = 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; }
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; }
html { text-shadow: 0 0 0 !important; }
.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; }
html { background: #fff !important; }
}
`;
if (filterStyle && filterStyle.textContent === css) return;
filterStyle = document.createElement('style');
filterStyle.id = CONFIG.FILTER_STYLE_ID;
filterStyle.textContent = css;
document.head.appendChild(filterStyle);
}
// ==================== 预加载与立即应用(强化首次启动) ====================
function preGenerateCommonCSS() {
const commonModes = ['dark', 'classic', 'soft', 'eyecare', 'deep', 'pure'];
for (const mode of commonModes) {
if (!cachedCSS.has(mode)) {
const css = generateFullStyleCSS(mode);
if (css) cachedCSS.set(mode, css);
}
}
}
// 核心:立即尝试应用样式(不依赖任何事件)
function tryImmediateApply() {
if (immediateApplyDone) return;
if (Util.isBlacklisted() || Util.isFlickerSuppressSite()) {
immediateApplyDone = true;
return;
}
const mode = getEffectiveMode();
if (mode === 'light') {
immediateApplyDone = true;
return;
}
// 对于覆盖层模式,直接创建覆盖层(html 元素始终存在)
if (mode === 'green' || mode === 'warm') {
applyOverlay(mode);
immediateApplyDone = true;
return;
}
// 对于滤镜模式,需要等待 head 创建 SVG 元素
if (mode === 'filter') {
if (document.head) {
applyFilterMode();
immediateApplyDone = true;
} else {
// 等待 head 出现
if (!headObserver) {
headObserver = new MutationObserver(() => {
if (document.head) {
headObserver.disconnect();
applyFilterMode();
immediateApplyDone = true;
}
});
headObserver.observe(document.documentElement, { childList: true, subtree: true });
}
}
return;
}
// CSS 模式:需要 head 来插入 style
const css = getCSSForMode(mode);
if (css && css !== '') {
if (document.head) {
Util.addStyle(CONFIG.STYLE_ID, css);
immediateApplyDone = true;
} else {
if (!headObserver) {
headObserver = new MutationObserver(() => {
if (document.head) {
headObserver.disconnect();
Util.addStyle(CONFIG.STYLE_ID, css);
immediateApplyDone = true;
}
});
headObserver.observe(document.documentElement, { childList: true, subtree: true });
}
}
} else {
immediateApplyDone = true;
}
}
function startPreload() {
if (preloadTimer) clearTimeout(preloadTimer);
const checkAndInject = () => {
if (earlyInjected) return;
if (Util.isBlacklisted() || Util.isFlickerSuppressSite()) {
earlyInjected = true;
return;
}
const mode = getEffectiveMode();
if (mode === 'green' || mode === 'warm' || mode === 'filter') {
earlyInjected = true;
return;
}
const css = getCSSForMode(mode);
if (css && css !== '') {
if (document.head) {
Util.addStyle(CONFIG.STYLE_ID, css);
earlyInjected = true;
return;
}
}
preloadRetryCount++;
if (preloadRetryCount < MAX_PRELOAD_RETRY) {
preloadTimer = setTimeout(checkAndInject, 10);
} else {
earlyInjected = true;
}
};
checkAndInject();
}
function earlyApplyStyles() {
if (earlyInjected) return;
const preloadEnabled = Util.getValue('PRELOAD_ENABLED');
const instantMode = Util.getValue('INSTANT_MODE');
if (!preloadEnabled && !instantMode) return;
startPreload();
}
// ==================== 模式管理辅助函数 ====================
function getCurrentGlobalMode() {
let mode = Util.getValue('currentMode');
if (!mode || !MODE_NAMES[mode]) mode = 'light';
return mode;
}
function getSiteSpecificMode(domain, ignoreTempDisable = false) {
let host = domain || location.host;
if (!ignoreTempDisable && Util.getValue('tempDisableAllSiteModes')) return null;
try {
let siteModesStr = Util.getValue('siteSpecificModes');
let siteModes = JSON.parse(siteModesStr);
let entry = siteModes[host];
if (!entry) return null;
if (typeof entry === 'string') {
if (VALID_MODES.includes(entry)) return entry;
delete siteModes[host];
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
return null;
}
if (typeof entry === 'object' && entry.mode && VALID_MODES.includes(entry.mode)) return entry;
delete siteModes[host];
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
return null;
} catch(e) { return null; }
}
function getGreenOpacityForSite(domain) {
let host = domain || location.host;
try {
let siteModesStr = Util.getValue('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 Util.getValue('globalGreenOpacity');
} catch(e) { return 0.3; }
}
function getWarmOpacityForSite(domain) {
let host = domain || location.host;
try {
let siteModesStr = Util.getValue('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 Util.getValue('globalWarmOpacity');
} catch(e) { return 0.3; }
}
function setSiteSpecificMode(domain, mode, opacity = null) {
let host = domain;
if (!host) return;
if (mode && mode !== 'default' && !VALID_MODES.includes(mode)) {
console.warn('无效的专用模式:', mode);
Util.showNotification(`模式 "${mode}" 无效,已忽略`, 'warning', 2000);
return;
}
try {
let siteModesStr = Util.getValue('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 = Math.min(0.95, Math.max(0, opacity));
siteModes[host] = { mode: mode, opacity: currentOpacity };
} else {
siteModes[host] = mode;
}
Util.setValue('siteSpecificModes', JSON.stringify(siteModes));
Util.showNotification(`已为 ${host} 设置专用模式: ${MODE_NAMES[mode] || mode}`, 'success', 1500);
} catch(e) { console.error('设置网站专用模式出错:', e); }
}
function shouldApplyGlobalMode() {
let globalEnable = Util.getValue('globalEnable');
let enableList = Util.getValue('enableList');
let forcedList = Util.getValue('forcedEnableList');
let host = location.host;
let basicEnabled = false;
if (forcedList.includes(host)) {
basicEnabled = (globalEnable || enableList.includes(host));
} else {
basicEnabled = (globalEnable || enableList.includes(host));
}
if (!basicEnabled) return false;
const inWhitelist = enableList.includes(host);
const avoidDarkSite = Util.getValue('AVOID_DARK_SITE');
if (avoidDarkSite && !inWhitelist) {
try {
const brightness = Util.detectPageBrightness();
const threshold = Util.getValue('BRIGHTNESS_THRESHOLD');
if (brightness < threshold) return false;
} catch(e) {}
}
return true;
}
function getEffectiveMode() {
const host = location.host;
if (Util.isBlacklisted()) return 'light';
const forceMode = getForceSpecialModeForSite();
if (forceMode !== null) return forceMode;
let siteSpecific = getSiteSpecificMode();
if (siteSpecific) {
if (typeof siteSpecific === 'string') return siteSpecific;
if (typeof siteSpecific === 'object' && siteSpecific.mode) return siteSpecific.mode;
}
if (shouldApplyGlobalMode()) {
return getCurrentGlobalMode();
}
return 'light';
}
// ==================== 强制恢复增强模块 ====================
function isStyleValidForMode(mode) {
if (mode === 'light') return true;
if (mode === 'green') {
return greenOverlay && document.documentElement.contains(greenOverlay) &&
greenOverlay.style.opacity === String(getGreenOpacityForSite());
}
if (mode === 'warm') {
return warmOverlay && document.documentElement.contains(warmOverlay) &&
warmOverlay.style.opacity === String(getWarmOpacityForSite());
}
if (mode === 'filter') {
if (!filterStyle || !document.head.contains(filterStyle)) return false;
return filterStyle.textContent.includes('feColorMatrix');
}
const styleEl = document.getElementById(CONFIG.STYLE_ID);
if (!styleEl || !document.head.contains(styleEl)) return false;
const expectedCSS = getCSSForMode(mode);
if (!expectedCSS) return false;
return styleEl.textContent.replace(/\s/g, '') === expectedCSS.replace(/\s/g, '');
}
function safeRecover(force = false) {
if (!Util.getValue('FORCE_RECOVERY')) return;
if (isRecovering) return;
isRecovering = true;
setTimeout(() => {
try {
const mode = getEffectiveMode();
if (!force && lastAppliedMode === mode && isStyleValidForMode(mode)) {
isRecovering = false;
return;
}
StyleManager.apply(true);
lastAppliedMode = getEffectiveMode();
} catch(e) {
console.warn('强制恢复失败', e);
} finally {
isRecovering = false;
}
}, CONFIG.RECOVERY_DEBOUNCE);
}
function startEnhancedRecovery() {
if (!Util.getValue('FORCE_RECOVERY')) return;
if (styleIntegrityTimer) clearInterval(styleIntegrityTimer);
styleIntegrityTimer = setInterval(() => {
if (Util.isBlacklisted()) return;
safeRecover(false);
}, CONFIG.RECOVERY_INTERVAL);
if (recoveryObserver) recoveryObserver.disconnect();
recoveryObserver = new MutationObserver((mutations) => {
if (!Util.getValue('FORCE_RECOVERY')) return;
if (Util.isBlacklisted()) return;
for (const mutation of mutations) {
if (mutation.type === 'childList' && mutation.removedNodes.length) {
for (const node of mutation.removedNodes) {
if (node.nodeType === 1) {
const id = node.id;
if (id === CONFIG.STYLE_ID || id === CONFIG.FILTER_STYLE_ID ||
id === 'eye-protect-green-overlay' || id === 'eye-protect-warm-overlay') {
safeRecover(true);
return;
}
}
}
}
}
});
const targetNode = document.head || document.documentElement;
if (targetNode) {
recoveryObserver.observe(targetNode, { childList: true, subtree: true });
} else {
setTimeout(() => startEnhancedRecovery(), 100);
}
}
// ==================== 页面返回修复 ====================
function patchHistoryAPI() {
try {
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
const forceReapply = () => {
if (navigationTimer) clearTimeout(navigationTimer);
navigationTimer = setTimeout(() => {
if (!Util.isBlacklisted()) {
StyleManager.apply(true);
ObserverManager.restart();
}
navigationTimer = null;
}, 50);
};
history.pushState = function() { originalPushState.apply(this, arguments); forceReapply(); };
history.replaceState = function() { originalReplaceState.apply(this, arguments); forceReapply(); };
window.addEventListener('popstate', forceReapply);
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
safeRecover(true);
} else {
forceReapply();
}
});
window.addEventListener('beforeunload', () => {
if (history.pushState !== originalPushState) history.pushState = originalPushState;
if (history.replaceState !== originalReplaceState) history.replaceState = originalReplaceState;
}, { once: true });
} catch(e) {}
}
function initVisibilityRecovery() {
const handleVisibilityChange = () => {
if (!document.hidden && Util.getValue('FORCE_RECOVERY')) {
safeRecover(true);
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
startEnhancedRecovery();
}
// ==================== ObserverManager ====================
const ObserverManager = (() => {
let mutationObserver = null, styleObserver = null, isActive = false, debounceTimer = null, isSuppressMode = false, keepAliveTimer = null, lastApplyTime = Date.now();
let observerNode = null, isDisposed = false;
function handleMutations(mutations) {
if (isDisposed) return;
lastApplyTime = Date.now();
if (debounceTimer) clearTimeout(debounceTimer);
const isSuppress = Util.isFlickerSuppressSite();
const delay = CONFIG.DEBOUNCE_DELAY;
const nodeThreshold = isSuppress ? 10 : 3;
debounceTimer = setTimeout(() => {
if (isDisposed) return;
const significantChange = mutations.some(m => m.type === 'childList' && m.addedNodes.length > nodeThreshold);
if (significantChange) {
if (isSuppress) { pause(); setTimeout(() => resume(), 50); }
StyleManager.apply(true);
}
debounceTimer = null;
}, delay);
}
function startKeepAlive() {
if (isDisposed) return;
if (keepAliveTimer) clearInterval(keepAliveTimer);
keepAliveTimer = setInterval(() => {
if (isDisposed) return;
if (Date.now() - lastApplyTime > 30000) { StyleManager.apply(true); lastApplyTime = Date.now(); }
}, 15000);
}
function stopKeepAlive() { if (keepAliveTimer) { clearInterval(keepAliveTimer); keepAliveTimer = null; } }
return {
start() {
if (isActive || isDisposed) return;
if (!document.body) { setTimeout(() => this.start(), 100); return; }
isActive = true; isDisposed = false; isSuppressMode = Util.isFlickerSuppressSite(); lastApplyTime = Date.now(); observerNode = document.body;
mutationObserver = new MutationObserver(handleMutations);
try { mutationObserver.observe(observerNode, { childList: true, subtree: true, attributes: false, characterData: false }); } catch(e) {}
if (Util.getValue('FORCE_RECOVERY')) {
styleObserver = new MutationObserver((mutations) => {
if (isSuppressMode || isDisposed) return;
for (let m of mutations) {
if (m.removedNodes.length) {
for (let node of m.removedNodes) {
if (node.nodeType === 1 && (node.id === CONFIG.STYLE_ID || node.id === CONFIG.FILTER_STYLE_ID || node.id === 'eye-protect-green-overlay' || node.id === 'eye-protect-warm-overlay')) {
setTimeout(() => { if (!isDisposed) StyleManager.apply(true); }, 20);
lastApplyTime = Date.now(); return;
}
}
}
}
});
try { styleObserver.observe(document.head || document.documentElement, { childList: true, subtree: false }); } catch(e) {}
}
startKeepAlive();
},
stop() {
isActive = false; stopKeepAlive();
if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = null; }
if (mutationObserver) { try { mutationObserver.disconnect(); mutationObserver = null; } catch(e) {} }
if (styleObserver) { try { styleObserver.disconnect(); styleObserver = null; } catch(e) {} }
observerNode = null; isSuppressMode = false;
},
pause() {
if (mutationObserver) try { mutationObserver.disconnect(); } catch(e) {}
if (styleObserver) try { styleObserver.disconnect(); } catch(e) {}
stopKeepAlive();
},
resume() {
if (!isActive || isDisposed) return;
if (!observerNode) observerNode = document.body;
startKeepAlive();
if (mutationObserver && observerNode) try { mutationObserver.observe(observerNode, { childList: true, subtree: true, attributes: false, characterData: false }); } catch(e) {}
if (styleObserver && Util.getValue('FORCE_RECOVERY')) try { styleObserver.observe(document.head || document.documentElement, { childList: true, subtree: false }); } catch(e) {}
},
restart() { this.stop(); setTimeout(() => this.start(), 50); },
dispose() { isDisposed = true; this.stop(); }
};
})();
// ==================== 样式管理器 ====================
const StyleManager = (() => {
function apply(force = false) {
const mode = getEffectiveMode();
if (mode === 'light') {
cleanAllEffects();
} else {
applyModeByMode(mode);
}
lastAppliedMode = mode;
}
function cleanAllEffects() {
Util.removeElementById(CONFIG.STYLE_ID);
removeOverlays();
removeFilterMode();
}
return { apply };
})();
// ==================== 切换模式核心函数 ====================
function getNextModeInCycle(currentMode, cycleModes) {
let modes = (cycleModes && Array.isArray(cycleModes) && cycleModes.length > 0) ? [...cycleModes] : ['dark'];
const idx = modes.indexOf(currentMode);
if (idx !== -1) {
const nextIdx = (idx + 1) % modes.length;
return modes[nextIdx];
} else {
if (currentMode === 'light') {
return modes[0];
}
if (modes.includes('light')) return 'light';
return modes[0];
}
}
function switchMode() {
let oldMode = Util.getValue('currentMode') || 'light';
let cycleModes = Util.getValue('globalCycleModes');
if (!Array.isArray(cycleModes) || cycleModes.length === 0) {
cycleModes = ['dark'];
Util.setValue('globalCycleModes', cycleModes);
}
const nextMode = getNextModeInCycle(oldMode, cycleModes);
Util.setValue('currentMode', nextMode);
handleAutoDisableTempModeOnModeChange(nextMode, oldMode);
StyleManager.apply(true);
setTimeout(() => {
refreshMenu();
Util.showNotification(`已切换到 ${getModeName(nextMode)}`, 'success', 1500);
scheduleUIUpdate();
}, 0);
}
// ==================== 菜单与设置面板 ====================
function clearMenu() { menuCommands.forEach(cmd => { try { GM_unregisterMenuCommand(cmd); } catch(e) {} }); menuCommands = []; }
function refreshMenu() { clearMenu(); initMenu(); }
function getModeName(mode) { return MODE_NAMES[mode] || '白天模式'; }
function getSiteModeName(mode) {
if (typeof mode === 'object' && mode.mode === 'green') return '🌱 豆沙绿';
if (typeof mode === 'object' && mode.mode === 'warm') return '🧡 深暖色';
return MODE_NAMES[mode] || '默认';
}
function handleAutoDisableTempModeOnModeChange(newMode, oldMode) {
const autoMode = Util.getValue('autoDisableTempMode');
if (!autoMode) return;
const alsoEnableForce = Util.getValue('autoDisableAlsoEnableForceSpecial');
if (newMode === autoMode && oldMode !== autoMode) {
let changed = false;
if (Util.getValue('tempDisableAllSiteModes') !== true) {
Util.setValue('tempDisableAllSiteModes', true);
changed = true;
}
if (alsoEnableForce && Util.getValue('forceSpecialModeEnabled') !== true) {
Util.setValue('forceSpecialModeEnabled', true);
changed = true;
}
if (changed) {
StyleManager.apply(true);
refreshMenu();
const msg = alsoEnableForce ? '已自动关闭专用模式并开启强制专用模式' : '已自动关闭专用模式';
Util.showNotification(`${msg}(进入 ${getModeName(newMode)} 模式)`, 'info', 2000);
}
}
else if (oldMode === autoMode && newMode !== autoMode) {
let changed = false;
if (Util.getValue('tempDisableAllSiteModes') !== false) {
Util.setValue('tempDisableAllSiteModes', false);
changed = true;
}
if (Util.getValue('forceSpecialModeEnabled') !== false) {
Util.setValue('forceSpecialModeEnabled', false);
changed = true;
}
if (changed) {
StyleManager.apply(true);
refreshMenu();
Util.showNotification(`已恢复专用模式(离开 ${getModeName(oldMode)} 模式)`, 'info', 2000);
}
}
}
let pendingUIUpdate = false;
function scheduleUIUpdate() {
if (pendingUIUpdate) return;
pendingUIUpdate = true;
requestAnimationFrame(() => {
if (currentSettingsShadow) {
updateGlobalConcentrationPanelVisibility(currentSettingsShadow);
refreshGlobalConcentrationPanel(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
pendingUIUpdate = false;
});
}
function updateGlobalConcentrationPanelVisibility(shadowRoot) {
if (!shadowRoot) return;
const currentGlobalMode = getCurrentGlobalMode();
const panel = shadowRoot.querySelector('#globalConcentrationPanel');
if (panel) {
if (currentGlobalMode === 'green' || currentGlobalMode === 'warm') {
panel.style.display = 'block';
} else {
panel.style.display = 'none';
}
}
}
function refreshGlobalConcentrationPanel(shadowRoot) {
if (!shadowRoot) return;
const currentGlobalMode = getCurrentGlobalMode();
const globalGreenPanel = shadowRoot.querySelector('#globalGreenPanel');
const globalWarmPanel = shadowRoot.querySelector('#globalWarmPanel');
if (globalGreenPanel) globalGreenPanel.style.display = currentGlobalMode === 'green' ? 'block' : 'none';
if (globalWarmPanel) globalWarmPanel.style.display = currentGlobalMode === 'warm' ? 'block' : 'none';
const globalGreenSlider = shadowRoot.querySelector('#globalGreenSlider');
const globalGreenSpan = shadowRoot.querySelector('#globalGreenValue');
if (globalGreenSlider && globalGreenSpan) {
const val = Util.getValue('globalGreenOpacity');
globalGreenSlider.value = val;
globalGreenSpan.textContent = Math.round(val * 100);
}
const globalWarmSlider = shadowRoot.querySelector('#globalWarmSlider');
const globalWarmSpan = shadowRoot.querySelector('#globalWarmValue');
if (globalWarmSlider && globalWarmSpan) {
const val = Util.getValue('globalWarmOpacity');
globalWarmSlider.value = val;
globalWarmSpan.textContent = Math.round(val * 100);
}
}
function isSpecialModeActive() {
if (Util.getValue('tempDisableAllSiteModes')) return false;
let siteSpecific = getSiteSpecificMode();
if (siteSpecific && siteSpecific !== 'default') return true;
if (isForceSpecialSite() && getForceSpecialModeForSite() !== null) return true;
return false;
}
function updateSettingsPanelUI(shadowRoot) {
if (!shadowRoot) return;
const currentGlobalMode = getCurrentGlobalMode();
const cycleModes = Util.getValue('globalCycleModes') || ['dark'];
const nextMode = getNextModeInCycle(currentGlobalMode, cycleModes);
const nextModeName = MODE_NAMES[nextMode] || '白天模式';
const toggleBtn = shadowRoot.querySelector('#toggleMode');
if (toggleBtn) toggleBtn.textContent = `切换 ${nextModeName}`;
const actualMode = getEffectiveMode();
const hasSpecial = isSpecialModeActive();
let modeDisplayText = getSiteModeName(actualMode);
if (hasSpecial) modeDisplayText += ' [专用]';
const modeDisplaySpan = shadowRoot.querySelector('.settings-section:first-child div:first-child span:last-child');
if (modeDisplaySpan) modeDisplaySpan.textContent = modeDisplayText;
const forceSpecialCheckbox = shadowRoot.querySelector('#forceSpecialEnabled');
if (forceSpecialCheckbox) {
forceSpecialCheckbox.checked = Util.getValue('forceSpecialModeEnabled');
}
const tempDisableCheckbox = shadowRoot.querySelector('#tempDisableAllSiteModes');
if (tempDisableCheckbox) {
tempDisableCheckbox.checked = Util.getValue('tempDisableAllSiteModes');
const tempDisableStatusText = shadowRoot.querySelector('#tempDisableStatusText');
if (tempDisableStatusText) {
tempDisableStatusText.textContent = Util.getValue('tempDisableAllSiteModes') ? '已关闭' : '已启用';
tempDisableStatusText.style.color = Util.getValue('tempDisableAllSiteModes') ? '#dc3545' : '#007bff';
}
}
refreshGlobalConcentrationPanel(shadowRoot);
}
function toggleGlobal() {
let current = Util.getValue('globalEnable');
Util.setValue('globalEnable', !current);
StyleManager.apply(true);
refreshMenu();
Util.showNotification(!current ? '已开启全局模式' : '已关闭全局模式', 'success', 1500);
if (currentSettingsShadow) {
updateGlobalConcentrationPanelVisibility(currentSettingsShadow);
refreshGlobalConcentrationPanel(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
function toggleCurrentSite() {
let enableList = Util.getValue('enableList');
let host = location.host;
if (enableList.includes(host)) {
enableList = enableList.filter(domain => domain !== host);
Util.setValue('enableList', enableList);
StyleManager.apply(true);
Util.showNotification('已在当前网站禁用护眼模式', 'success', 1500);
} else {
enableList.push(host);
Util.setValue('enableList', enableList);
StyleManager.apply(true);
Util.showNotification('已在当前网站启用护眼模式', 'success', 1500);
}
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
function toggleForceEnable() {
let forcedList = Util.getValue('forcedEnableList');
let host = location.host;
if (forcedList.includes(host)) {
forcedList = forcedList.filter(domain => domain !== host);
Util.showNotification('已取消强制启用当前网站', 'info', 1500);
} else {
forcedList.push(host);
Util.showNotification('已强制启用当前网站', 'success', 1500);
}
Util.setValue('forcedEnableList', forcedList);
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
function toggleTempDisableAllSiteModes() {
let current = Util.getValue('tempDisableAllSiteModes');
let newState = !current;
Util.setValue('tempDisableAllSiteModes', newState);
StyleManager.apply(true);
refreshMenu();
Util.showNotification(newState ? '已关闭专用模式,使用全局设置' : '已恢复专用模式', 'success', 1500);
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
function toggleBlacklist() {
let blacklist = Util.getValue('blacklist');
let host = location.host;
if (blacklist.includes(host)) {
blacklist = blacklist.filter(domain => domain !== host);
Util.setValue('blacklist', blacklist);
Util.showNotification('已将当前网站从黑名单中移除', 'success', 1500);
StyleManager.apply(true);
} else {
blacklist.push(host);
Util.setValue('blacklist', blacklist);
Util.showNotification('已将当前网站添加到黑名单', 'info', 1500);
StyleManager.apply(true);
}
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
function clearBlacklist() {
if (confirm('确定要清空黑名单吗?这将移除所有在黑名单中的网站。')) {
Util.setValue('blacklist', []);
Util.showNotification('已清空黑名单', 'success', 1500);
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
}
function clearForcedList() {
if (confirm('确定要清空强制启用列表吗?')) {
Util.setValue('forcedEnableList', []);
Util.showNotification('已清空强制启用列表', 'success', 1500);
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) updateSettingsPanelUI(currentSettingsShadow);
}
}
function addCurrentToFlickerSuppress() {
let host = location.host;
let list = Util.getValue('FLICKER_SUPPRESS_LIST') || [];
const exists = list.some(item => {
let domain = typeof item === 'string' ? item : item.domain;
return domain === host;
});
if (exists) {
Util.showNotification('当前网站已在闪烁抑制列表中', 'info', 1500);
return;
}
list.push({ domain: host, addedTime: Date.now() });
Util.setValue('FLICKER_SUPPRESS_LIST', list);
Util.showNotification(`已将 ${host} 添加到闪烁抑制列表`, 'success', 1500);
refreshMenu();
if (currentSettingsShadow) {
refreshFlickerListUI(currentSettingsShadow);
}
}
function removeFromFlickerSuppress(domain) {
let list = Util.getValue('FLICKER_SUPPRESS_LIST') || [];
let newList = list.filter(item => {
let d = typeof item === 'string' ? item : item.domain;
return d !== domain;
});
Util.setValue('FLICKER_SUPPRESS_LIST', newList);
Util.showNotification(`已从闪烁抑制列表移除 ${domain}`, 'success', 1500);
refreshMenu();
if (currentSettingsShadow) {
refreshFlickerListUI(currentSettingsShadow);
}
}
function clearAllFlickerSuppress() {
if (confirm('确定要清空整个闪烁抑制列表吗?')) {
Util.setValue('FLICKER_SUPPRESS_LIST', []);
Util.showNotification('已清空闪烁抑制列表', 'success', 1500);
refreshMenu();
if (currentSettingsShadow) {
refreshFlickerListUI(currentSettingsShadow);
}
}
}
function addCurrentToForceSpecial() {
let host = location.host;
if (addToForceSpecialList(host)) {
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) {
refreshForceSpecialListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
} else {
Util.showNotification('当前网站已在强制专用模式列表中', 'info', 1500);
}
}
function removeFromForceSpecial(domain) {
removeFromForceSpecialList(domain);
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) {
refreshForceSpecialListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
function clearForceSpecial() {
if (confirm('确定要清空强制专用模式列表吗?')) {
clearForceSpecialList();
StyleManager.apply(true);
refreshMenu();
if (currentSettingsShadow) {
refreshForceSpecialListUI(currentSettingsShadow);
updateSettingsPanelUI(currentSettingsShadow);
}
}
}
// ==================== 列表渲染辅助函数 ====================
function renderListWithCurrentFirst(listObjects, currentHost) {
if (!listObjects || listObjects.length === 0) return '
暂无网站
';
let currentItem = null;
let otherItems = [];
for (let item of listObjects) {
let domain = item.domain;
if (domain === currentHost) {
currentItem = item;
} else {
otherItems.push(item);
}
}
otherItems.sort((a, b) => b.addedTime - a.addedTime);
let allItems = [];
if (currentItem) allItems.push(currentItem);
allItems.push(...otherItems);
let html = '';
allItems.forEach(item => {
let domain = item.domain;
html += `