Twitter/X Auto Dark Theme Switch

Enhanced theme management for Twitter/X with native support detection and fallback

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Twitter/X Auto Dark Theme Switch
// @version      0.1.7
// @description  Enhanced theme management for Twitter/X with native support detection and fallback
// @icon         https://raw.githubusercontent.com/TiancongLx/Tempermonkey/refs/heads/main/twitter_auto_dark_theme_switch/icon_X.png
// @license      WTFPL
// @author       Original: 13xforever, Enhanced: tianconglx
// @match        https://twitter.com/*
// @match        https://x.com/*
// @run-at       document-start
// @namespace    https://github.com/TiancongLx
// ==/UserScript==


(function() {
    "use strict";

    /**
     * Twitter/X 主题管理器
     * - 检测原生自动主题功能
     * - 在没有原生支持时提供自动主题切换
     * - 支持优雅降级和渐进增强
     */
    class XThemeManager {
        constructor() {
            // 配置常量
            this.CONFIG = {
                THEMES: {
                    LIGHT: "0",  // X 的亮色主题值
                    DIM:   "1",  // X 的暗色主题值
                    DARK:  "2",  // X 的深色主题值(备用)
                },
                COOKIE: {
                    NAME: "night_mode",
                    DOMAINS: ['.twitter.com', '.x.com'],
                    MAX_AGE: 365 * 24 * 60 * 60  // 一年的秒数
                },
                DETECTION: {
                    INTERVAL: 2000,  // 检测间隔(毫秒)
                    MAX_ATTEMPTS: 5  // 最大检测次数
                },
                DEBUG: false  // 是否启用调试日志
            };

            // 内部状态
            this.state = {
                enabled: false,
                nativeSupport: false,
                mediaQuery: null,
                eventListeners: new Set()
            };
        }

        /**
         * 初始化主题管理器
         * @returns {Promise<void>}
         */
        async initialize() {
            this.log('Initializing theme manager...');

            try {
                // 检查浏览器特性支持
                if (!this.checkBrowserSupport()) {
                    this.log('Browser features not supported', 'warn');
                    return;
                }

                // 检查是否存在原生自动主题功能
                const hasNativeSupport = await this.checkNativeAutoTheme();

                if (hasNativeSupport) {
                    this.log('Native auto theme detected, script disabled');
                    this.state.nativeSupport = true;
                    return;
                }

                // 如果没有原生支持,启用脚本功能
                this.enableScriptTheme();
                this.log('Script theme management enabled');
            } catch (error) {
                this.log(`Initialization failed: ${error.message}`, 'error');
                throw error;
            }
        }

        /**
         * 检查浏览器必要特性支持
         * @returns {boolean}
         */
        checkBrowserSupport() {
            return window.matchMedia &&
                   document.cookie !== undefined &&
                   window.localStorage;
        }

        /**
         * 检测原生自动主题功能
         * @returns {Promise<boolean>}
         */
        async checkNativeAutoTheme() {
            let checksRemaining = this.CONFIG.DETECTION.MAX_ATTEMPTS;

            const checkFeatures = () => {
                return new Promise(resolve => {
                    const check = () => {
                        // 1. 检查设置面板中的自动主题选项
                        const hasToggle = !!document.querySelector('[data-testid="auto-theme-switch"]');

                        // 2. 检查主题相关的 CSS 变量
                        const root = document.documentElement;
                        const hasCSS = window.getComputedStyle(root)
                            .getPropertyValue('--auto-theme-enabled') === 'true';

                        // 3. 检查可能存在的主题 API
                        const hasAPI = window.__X_THEME_API__?.autoThemeSupported;

                        if (hasToggle || hasCSS || hasAPI) {
                            resolve(true);
                            return;
                        }

                        checksRemaining--;
                        if (checksRemaining > 0) {
                            setTimeout(check, this.CONFIG.DETECTION.INTERVAL);
                        } else {
                            resolve(false);
                        }
                    };

                    check();
                });
            };

            return await checkFeatures();
        }

        /**
         * 启用脚本的主题管理功能
         */
        enableScriptTheme() {
            // 设置媒体查询
            this.state.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

            // 创建主题变化处理函数
            const handleThemeChange = (e) => {
                const theme = e.matches ? this.CONFIG.THEMES.DIM : this.CONFIG.THEMES.LIGHT;
                this.setThemeCookie(theme);
            };

            // 初始设置
            handleThemeChange(this.state.mediaQuery);

            // 添加事件监听
            this.state.mediaQuery.addEventListener('change', handleThemeChange);
            this.state.eventListeners.add({
                target: this.state.mediaQuery,
                type: 'change',
                handler: handleThemeChange
            });

            this.state.enabled = true;

            // 添加原生主题支持的 meta 标签
            this.addColorSchemeMeta();
        }

        /**
         * 设置主题 cookie
         * @param {string} theme - 主题值
         */
        setThemeCookie(theme) {
            try {
                const { NAME, DOMAINS, MAX_AGE } = this.CONFIG.COOKIE;
                const cookieString = `${NAME}=${theme}; path=/; secure; max-age=${MAX_AGE}`;

                DOMAINS.forEach(domain => {
                    document.cookie = `${cookieString}; domain=${domain}`;
                });

                this.log(`Theme cookie set to: ${theme}`);
            } catch (error) {
                this.log(`Failed to set theme cookie: ${error.message}`, 'error');
            }
        }

        /**
         * 添加 color-scheme meta 标签
         */
        addColorSchemeMeta() {
            if (!document.querySelector('meta[name="color-scheme"]')) {
                const meta = document.createElement('meta');
                meta.name = 'color-scheme';
                meta.content = 'light dark';
                document.head.appendChild(meta);
                this.log('Added color-scheme meta tag');
            }
        }

        /**
         * 禁用主题管理器
         */
        disable() {
            if (!this.state.enabled) return;

            // 清理所有事件监听
            this.state.eventListeners.forEach(({ target, type, handler }) => {
                target.removeEventListener(type, handler);
            });
            this.state.eventListeners.clear();

            this.state.enabled = false;
            this.log('Theme manager disabled');
        }

        /**
         * 日志工具方法
         * @param {string} message - 日志消息
         * @param {'log'|'warn'|'error'} level - 日志级别
         */
        log(message, level = 'log') {
            if (!this.CONFIG.DEBUG) return;

            const prefix = '[X Theme Manager]';
            switch (level) {
                case 'warn':
                    console.warn(`${prefix} ${message}`);
                    break;
                case 'error':
                    console.error(`${prefix} ${message}`);
                    break;
                default:
                    console.log(`${prefix} ${message}`);
            }
        }
    }

    // 脚本启动逻辑
    const startScript = () => {
        const themeManager = new XThemeManager();

        // 将实例绑定到 window 对象,方便调试
        if (themeManager.CONFIG.DEBUG) {
            window.__X_THEME_MANAGER__ = themeManager;
        }

        // 初始化主题管理器
        themeManager.initialize().catch(error => {
            console.error('[X Theme Manager] Failed to initialize:', error);
        });
    };

    // 根据文档加载状态决定启动时机
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', startScript);
    } else {
        startScript();
    }
})();