Greasy Fork

来自缓存

Greasy Fork is available in English.

智能夜间模式

实现任意网站的夜间模式,自动跳过原生暗色网站

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name              智能夜间模式
// @version           1.0.0
// @description       实现任意网站的夜间模式,自动跳过原生暗色网站
// @author            Phoebe
// @license           MIT
// @match             *://*/*
// @require           https://unpkg.com/[email protected]/dist/rule.min.js
// @run-at            document-start
// @grant             GM_getValue
// @grant             GM_setValue
// @icon              data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNOTMuNSA5NC42YzEwLjYgMCAyMC4zLTMuMyAyOC4yLTktOC4zIDIyLjUtMzAuMiAzOC42LTU2IDM4LjYtMzIuNyAwLTU5LjMtMjUuOC01OS4zLTU3LjdTMzIuOSA4LjcgNjUuNyA4LjdoMi4yQzU0LjYgMTcgNDUuNyAzMS41IDQ1LjcgNDhjMCAyNS43IDIxLjQgNDYuNiA0Ny44IDQ2LjZ6IiBmaWxsPSIjZmZiNTc4Ii8+PHBhdGggZD0iTTEyMS42IDgxLjhjLS44IDAtMS42LjItMi4zLjctNy41IDUuMy0xNi41IDguMS0yNS44IDguMS0yNC4yIDAtNDMuOS0xOS4xLTQzLjktNDIuNyAwLTE0LjcgNy42LTI4LjEgMjAuMy0zNiAxLjEtLjcgMS44LTEuOSAxLjgtMy4yYTMuOCAzLjggMCAwIDAtMy44LTMuOGgtMi4zQzMwLjggNC45IDIuNSAzMi41IDIuNSA2Ni41UzMwLjggMTI4IDY1LjcgMTI4YzI2LjcgMCA1MC43LTE2LjUgNTkuNi00MSAuMS0uNC4yLS45LjItMS4zIDAtMi4xLTEuNy0zLjktMy45LTMuOXptLTU1LjkgMzguNWMtMzAuNSAwLTU1LjQtMjQuMi01NS40LTUzLjkgMC0yNi4yIDE5LjQtNDguNiA0NS43LTUzLjEtOSA5LjQtMTQuMiAyMS44LTE0LjIgMzQuNyAwIDI3LjggMjMuMiA1MC40IDUxLjcgNTAuNCA2LjcgMCAxMy4yLTEuMiAxOS4zLTMuNi0xMCAxNS44LTI3LjggMjUuNS00Ny4xIDI1LjV6bTM1LjYtNDcuOUg3Ny45Yy0xLjYgMC0yLjktMS4zLTIuOS0yLjkgMC0xIC41LTEuOSAxLjMtMi40TDkxLjYgNTdINzcuOWMtMS42IDAtMi45LTEuMy0yLjktMi45czEuMy0yLjkgMi45LTIuOWgyMy40YzEuNiAwIDIuOSAxLjMgMi45IDIuOSAwIDEtLjUgMS45LTEuMyAyLjRMODcuNiA2Ni42aDEzLjdjMS42IDAgMi45IDEuMyAyLjkgMi45cy0xLjMgMi45LTIuOSAyLjl6bTEzLjItMzEuMWgtMTQuNGMtMS42IDAtMi45LTEuMy0yLjktMi45IDAtMSAuNS0xLjkgMS4zLTIuNGw2LjMtNC4xaC00LjdjLTEuNiAwLTIuOS0xLjMtMi45LTIuOXMxLjMtMi45IDIuOS0yLjloMTQuNGMxLjYgMCAyLjkgMS4zIDIuOSAyLjkgMCAxLS41IDEuOS0xLjMgMi40bC02LjMgNC4xaDQuN2MxLjYgMCAyLjkgMS4zIDIuOSAyLjlzLTEuMyAyLjktMi45IDIuOXptNS42LTI3LjVIMTA4Yy0xLjYgMC0yLjktMS4zLTIuOS0yLjkgMC0xIC41LTEuOSAxLjMtMi40bDQuMS0yLjdIMTA4Yy0xLjYgMC0yLjktMS4zLTIuOS0yLjlTMTA2LjQgMCAxMDggMGgxMi4xYzEuNiAwIDIuOSAxLjMgMi45IDIuOSAwIDEtLjUgMS45LTEuMyAyLjRMMTE3LjYgOGgyLjRjMS42IDAgMi45IDEuMyAyLjkgMi45cy0xLjIgMi45LTIuOCAyLjl6IiBmaWxsPSIjNDQ0Ii8+PC9zdmc+
// @namespace http://greasyfork.icu/users/1292139
// ==/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,<svg xmlns="http://www.w3.org/2000/svg"><filter id="dark-mode-filter" color-interpolation-filters="sRGB"><feColorMatrix type="matrix" values="0.283 -0.567 -0.567 0 0.925 -0.567 0.283 -0.567 0 0.925 -0.567 -0.567 0.283 0 0.925 0 0 0 1 0"/></filter></svg>#dark-mode-filter') !important;`,
        firefoxReverseFilter: `filter: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg"><filter id="dark-mode-reverse-filter" color-interpolation-filters="sRGB"><feColorMatrix type="matrix" values="0.333 -0.667 -0.667 0 1 -0.667 0.333 -0.667 0 1 -0.667 -0.667 0.333 0 1 0 0 0 1 0"/></filter></svg>#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 = '<svg id="dark-mode-svg" style="height: 0; width: 0;"><filter id="dark-mode-filter" x="0" y="0" width="99999" height="99999"><feColorMatrix type="matrix" values="0.283 -0.567 -0.567 0 0.925 -0.567 0.283 -0.567 0 0.925 -0.567 -0.567 0.283 0 0.925 0 0 0 1 0"></feColorMatrix></filter><filter id="dark-mode-reverse-filter" x="0" y="0" width="99999" height="99999"><feColorMatrix type="matrix" values="0.333 -0.667 -0.667 0 1 -0.667 0.333 -0.667 0 1 -0.667 -0.667 0.333 0 1 0 0 0 1 0"></feColorMatrix></filter></svg>';
            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();
})();