// ==UserScript==
// @name 智能夜间模式
// @version 1.0.0
// @description 实现任意网站的夜间模式,自动跳过原生暗色网站
// @author Phoebe
// @license MIT
// @match *://*/*
// @require https://unpkg.com/darkrule@1.0.4/dist/rule.min.js
// @run-at document-start
// @grant GM_getValue
// @grant GM_setValue
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNOTMuNSA5NC42YzEwLjYgMCAyMC4zLTMuMyAyOC4yLTktOC4zIDIyLjUtMzAuMiAzOC42LTU2IDM4LjYtMzIuNyAwLTU5LjMtMjUuOC01OS4zLTU3LjdTMzIuOSA4LjcgNjUuNyA4LjdoMi4yQzU0LjYgMTcgNDUuNyAzMS41IDQ1LjcgNDhjMCAyNS43IDIxLjQgNDYuNiA0Ny44IDQ2LjZ6IiBmaWxsPSIjZmZiNTc4Ii8+PHBhdGggZD0iTTEyMS42IDgxLjhjLS44IDAtMS42LjItMi4zLjctNy41IDUuMy0xNi41IDguMS0yNS44IDguMS0yNC4yIDAtNDMuOS0xOS4xLTQzLjktNDIuNyAwLTE0LjcgNy42LTI4LjEgMjAuMy0zNiAxLjEtLjcgMS44LTEuOSAxLjgtMy4yYTMuOCAzLjggMCAwIDAtMy44LTMuOGgtMi4zQzMwLjggNC45IDIuNSAzMi41IDIuNSA2Ni41UzMwLjggMTI4IDY1LjcgMTI4YzI2LjcgMCA1MC43LTE2LjUgNTkuNi00MSAuMS0uNC4yLS45LjItMS4zIDAtMi4xLTEuNy0zLjktMy45LTMuOXptLTU1LjkgMzguNWMtMzAuNSAwLTU1LjQtMjQuMi01NS40LTUzLjkgMC0yNi4yIDE5LjQtNDguNiA0NS43LTUzLjEtOSA5LjQtMTQuMiAyMS44LTE0LjIgMzQuNyAwIDI3LjggMjMuMiA1MC40IDUxLjcgNTAuNCA2LjcgMCAxMy4yLTEuMiAxOS4zLTMuNi0xMCAxNS44LTI3LjggMjUuNS00Ny4xIDI1LjV6bTM1LjYtNDcuOUg3Ny45Yy0xLjYgMC0yLjktMS4zLTIuOS0yLjkgMC0xIC41LTEuOSAxLjMtMi40TDkxLjYgNTdINzcuOWMtMS42IDAtMi45LTEuMy0yLjktMi45czEuMy0yLjkgMi45LTIuOWgyMy40YzEuNiAwIDIuOSAxLjMgMi45IDIuOSAwIDEtLjUgMS45LTEuMyAyLjRMODcuNiA2Ni42aDEzLjdjMS42IDAgMi45IDEuMyAyLjkgMi45cy0xLjMgMi45LTIuOSAyLjl6bTEzLjItMzEuMWgtMTQuNGMtMS42IDAtMi45LTEuMy0yLjktMi45IDAtMSAuNS0xLjkgMS4zLTIuNGw2LjMtNC4xaC00LjdjLTEuNiAwLTIuOS0xLjMtMi45LTIuOXMxLjMtMi45IDIuOS0yLjloMTQuNGMxLjYgMCAyLjkgMS4zIDIuOSAyLjkgMCAxLS41IDEuOS0xLjMgMi40bC02LjMgNC4xaDQuN2MxLjYgMCAyLjkgMS4zIDIuOSAyLjlzLTEuMyAyLjktMi45IDIuOXptNS42LTI3LjVIMTA4Yy0xLjYgMC0yLjktMS4zLTIuOS0yLjkgMC0xIC41LTEuOSAxLjMtMi40bDQuMS0yLjdIMTA4Yy0xLjYgMC0yLjktMS4zLTIuOS0yLjlTMTA2LjQgMCAxMDggMGgxMi4xYzEuNiAwIDIuOSAxLjMgMi45IDIuOSAwIDEtLjUgMS45LTEuMyAyLjRMMTE3LjYgOGgyLjRjMS42IDAgMi45IDEuMyAyLjkgMi45cy0xLjIgMi45LTIuOCAyLjl6IiBmaWxsPSIjNDQ0Ii8+PC9zdmc+
// @namespace https://greasyfork.org/users/1292139
// @downloadURL https://update.greasyfork.icu/scripts/575332/%E6%99%BA%E8%83%BD%E5%A4%9C%E9%97%B4%E6%A8%A1%E5%BC%8F.user.js
// @updateURL https://update.greasyfork.icu/scripts/575332/%E6%99%BA%E8%83%BD%E5%A4%9C%E9%97%B4%E6%A8%A1%E5%BC%8F.meta.js
// ==/UserScript==
;(function () {
'use strict';
let util = {
getValue(name) {
return GM_getValue(name);
},
setValue(name, value) {
GM_setValue(name, value);
},
addStyle(id, tag, css) {
tag = tag || 'style';
let doc = document, styleDom = doc.getElementById(id);
if (styleDom) return;
let style = doc.createElement(tag);
style.rel = 'stylesheet';
style.id = id;
tag === 'style' ? style.innerHTML = css : style.href = css;
doc.head.appendChild(style);
},
hover(ele, fn1, fn2) {
ele.onmouseenter = function () { //移入事件
fn1.call(ele);
};
ele.onmouseleave = function () { //移出事件
fn2.call(ele);
};
},
addThemeColor(color) {
let doc = document, meta = doc.getElementsByName('theme-color')[0];
if (meta) return meta.setAttribute('content', color);
let metaEle = doc.createElement('meta');
metaEle.name = 'theme-color';
metaEle.content = color;
doc.head.appendChild(metaEle);
},
getThemeColor() {
let meta = document.getElementsByName('theme-color')[0];
if (meta) {
return meta.content;
}
return '#ffffff';
},
removeElementById(eleId) {
let ele = document.getElementById(eleId);
ele && ele.parentNode.removeChild(ele);
},
hasElementById(eleId) {
return document.getElementById(eleId);
},
filter: '-webkit-filter: url(#dark-mode-filter) !important; filter: url(#dark-mode-filter) !important;',
reverseFilter: '-webkit-filter: url(#dark-mode-reverse-filter) !important; filter: url(#dark-mode-reverse-filter) !important;',
firefoxFilter: `filter: url('data:image/svg+xml;utf8,#dark-mode-filter') !important;`,
firefoxReverseFilter: `filter: url('data:image/svg+xml;utf8,#dark-mode-reverse-filter') !important;`,
noneFilter: '-webkit-filter: none !important; filter: none !important;',
};
let nativeDark = {
cacheKey() {
return 'native_dark_cache_v2:' + location.host;
},
cacheTTL: 7 * 24 * 60 * 60 * 1000,
readCache() {
let data = util.getValue(this.cacheKey());
if (!data || !data.time || Date.now() - data.time > this.cacheTTL) return null;
return data.value === true;
},
writeCache(value) {
util.setValue(this.cacheKey(), {
value: value === true,
time: Date.now()
});
},
parseColor(element) {
if (!element) return null;
let color = window.getComputedStyle(element).backgroundColor;
let match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([.\d]+))?/);
if (!match) return null;
if (match[4] !== undefined && Number(match[4]) === 0) return null;
return {
r: Number(match[1]),
g: Number(match[2]),
b: Number(match[3])
};
},
luminance(color) {
return color ? ((color.r * 299) + (color.g * 587) + (color.b * 114)) / 1000 : 255;
},
isDarkColor(color) {
return this.luminance(color) < 96;
},
isLightColor(color) {
return this.luminance(color) > 170;
},
hasDarkToken(value) {
return /(^|[\s_-])dark($|[\s_-])/.test(String(value || '').toLowerCase());
},
hasExplicitDarkTheme(element) {
if (!element) return false;
return this.hasDarkToken(element.className) ||
this.hasDarkToken(element.dataset && element.dataset.darkTheme) ||
this.hasDarkToken(element.dataset && element.dataset.theme) ||
this.hasDarkToken(element.dataset && element.dataset.colorMode);
},
visibleBackgroundColorAt(x, y) {
let element = document.elementFromPoint(x, y);
while (element) {
let color = this.parseColor(element);
if (color) return color;
element = element.parentElement;
}
return this.parseColor(document.body) || this.parseColor(document.documentElement);
},
visiblePageLooksDark() {
if (!document.body || !document.documentElement || !document.elementFromPoint) return false;
let width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
let height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
let points = [
[0.5, 0.12],
[0.25, 0.28],
[0.5, 0.28],
[0.75, 0.28],
[0.25, 0.5],
[0.5, 0.5],
[0.75, 0.5],
[0.25, 0.78],
[0.5, 0.78],
[0.75, 0.78]
];
let dark = 0;
let light = 0;
let detected = 0;
for (let point of points) {
let color = this.visibleBackgroundColorAt(Math.floor(width * point[0]), Math.floor(height * point[1]));
if (!color) continue;
detected += 1;
if (this.isDarkColor(color)) dark += 1;
if (this.isLightColor(color)) light += 1;
}
return detected > 0 && dark / detected >= 0.7 && light === 0;
},
pageLooksNativeDark() {
let visiblyDark = this.visiblePageLooksDark();
if (!visiblyDark) return false;
let colorScheme = window.getComputedStyle(document.documentElement).colorScheme;
if (this.hasExplicitDarkTheme(document.documentElement) ||
this.hasExplicitDarkTheme(document.body) ||
/^\s*dark\s*$/i.test(colorScheme)) {
return true;
}
return true;
},
detect() {
let cached = this.readCache();
if (cached !== null) return cached;
let result = this.pageLooksNativeDark();
this.writeCache(result);
return result;
}
};
let main = {
/**
* 配置默认值
*/
initValue() {
let value = [{
name: 'dark_mode',
value: 'dark'
}, {
name: 'exclude_list',
value: ['youku.com', 'v.youku.com', 'www.douyu.com', 'www.iqiyi.com', 'vip.iqiyi.com', 'mail.qq.com', 'live.kuaishou.com']
}, {
name: 'origin_theme_color',
value: '#ffffff'
}];
value.forEach((v) => {
util.getValue(v.name) === undefined && util.setValue(v.name, v.value);
});
},
addExtraStyle() {
try {
return darkModeRule;
} catch (e) {
return '';
}
},
createDarkFilter() {
if (util.hasElementById('dark-mode-svg')) return;
let svgDom = '';
let div = document.createElementNS('http://www.w3.org/1999/xhtml', 'div');
div.innerHTML = svgDom;
let frag = document.createDocumentFragment();
while (div.firstChild)
frag.appendChild(div.firstChild);
document.head.appendChild(frag);
},
createDarkStyle() {
util.addStyle('dark-mode-style', 'style', `
@media screen {
html {
${this.isFirefox() ? util.firefoxFilter : util.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,
.sr-reader,
.no-dark-mode,
.sr-backdrop {
${this.isFirefox() ? util.firefoxReverseFilter : util.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 {
${util.noneFilter}
}
/* 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 * {
${util.noneFilter}
}
::-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;
}
${this.addExtraStyle()}
}
@media print {
.no-print {
display: none !important;
}
}`);
},
setThemeColor() {
util.setValue('origin_theme_color', util.getThemeColor());
},
enableDarkMode() {
if (this.isFullScreen()) return;
if (this.shouldSkipNativeDark()) return;
!this.isFirefox() && this.createDarkFilter();
this.createDarkStyle();
util.addThemeColor('#131313');
this.scheduleNativeDarkCheck();
},
disableDarkMode() {
util.removeElementById('dark-mode-svg');
util.removeElementById('dark-mode-style');
util.addThemeColor(util.getValue('origin_theme_color'));
},
shouldSkipNativeDark() {
let cached = nativeDark.readCache();
if (cached === true) {
this.disableDarkMode();
return true;
}
if (cached === false) return false;
return false;
},
scheduleNativeDarkCheck() {
if (this.nativeDarkCheckStarted) return;
this.nativeDarkCheckStarted = true;
let run = () => setTimeout(() => this.verifyNativeDarkMode(), 150);
if (document.body) {
run();
return;
}
const observer = new MutationObserver(() => {
if (!document.body) return;
observer.disconnect();
run();
});
observer.observe(document.documentElement, {childList: true, subtree: true});
},
verifyNativeDarkMode() {
if (nativeDark.readCache() !== null) return;
const root = document.documentElement;
const oldVisibility = root.style.getPropertyValue('visibility');
const oldVisibilityPriority = root.style.getPropertyPriority('visibility');
root.style.setProperty('visibility', 'hidden', 'important');
this.disableDarkMode();
const isNativeDark = nativeDark.detect();
if (!isNativeDark && !this.isInExcludeList()) {
!this.isFirefox() && this.createDarkFilter();
this.createDarkStyle();
util.addThemeColor('#131313');
}
requestAnimationFrame(() => {
if (oldVisibility) {
root.style.setProperty('visibility', oldVisibility, oldVisibilityPriority);
} else {
root.style.removeProperty('visibility');
}
});
},
isTopWindow() {
return window.self === window.top;
},
addListener() {
document.addEventListener("fullscreenchange", (e) => {
if (this.isFullScreen()) {
//进入全屏
this.disableDarkMode();
} else {
//退出全屏
this.enableDarkMode();
}
});
},
isDarkMode() {
return true;
},
isInExcludeList() {
return util.getValue('exclude_list').includes(location.host);
},
isFullScreen() {
return document.fullscreenElement;
},
isFirefox() {
return /Firefox/i.test(navigator.userAgent);
},
firstEnableDarkMode() {
if (document.head) {
this.enableDarkMode();
}
const headObserver = new MutationObserver(() => {
this.enableDarkMode();
});
headObserver.observe(document.head, {childList: true, subtree: true});
},
init() {
this.initValue();
this.setThemeColor();
if (this.isInExcludeList()) return;
this.addListener();
this.firstEnableDarkMode();
}
};
main.init();
})();