时间修改器

修改浏览器JS获取的本地时间,支持偏移和固定时间设置

当前为 2025-05-27 提交的版本,查看 最新版本

// ==UserScript==
// @name         时间修改器
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  修改浏览器JS获取的本地时间,支持偏移和固定时间设置
// @author       You
// @match        *://*/*
// @grant        none
// @run-at       document-start
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 配置存储键名
    const STORAGE_KEY = 'TIME_MODIFIER_CONFIG';

    // 默认配置
    const defaultConfig = {
        enabled: false,
        mode: 'offset', // 'offset' 或 'fixed'
        offsetHours: 0,
        offsetMinutes: 0,
        fixedDateTime: '',
        enabledDomains: [],
        uiCollapsed: false
    };

    // 获取当前域名
    const currentDomain = window.location.hostname;

    // 加载配置
    function loadConfig() {
        try {
            const stored = localStorage.getItem(STORAGE_KEY);
            return stored ? { ...defaultConfig, ...JSON.parse(stored) } : defaultConfig;
        } catch (e) {
            return defaultConfig;
        }
    }

    // 保存配置
    function saveConfig(config) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
    }

    // 检查当前域名是否启用
    function isDomainEnabled(config) {
        return config.enabledDomains.includes(currentDomain) || config.enabledDomains.length === 0;
    }

    // 计算时间偏移
    function calculateTimeOffset(config) {
        if (config.mode === 'offset') {
            return (config.offsetHours * 60 + config.offsetMinutes) * 60 * 1000;
        } else if (config.mode === 'fixed' && config.fixedDateTime) {
            const fixedTime = new Date(config.fixedDateTime).getTime();
            const currentTime = Date.now();
            return fixedTime - currentTime;
        }
        return 0;
    }

    // 修改时间相关的原生方法
    function modifyTimeMethods(offsetMs) {
        const originalDate = window.Date;
        const originalNow = Date.now;
        const originalGetTime = Date.prototype.getTime;

        // 重写 Date 构造函数
        window.Date = function(...args) {
            if (args.length === 0) {
                const modifiedTime = new originalDate(originalNow() + offsetMs);
                return modifiedTime;
            }
            return new originalDate(...args);
        };

        // 继承原型
        window.Date.prototype = originalDate.prototype;

        // 重写静态方法
        window.Date.now = function() {
            return originalNow() + offsetMs;
        };

        // 重写其他静态方法
        Object.getOwnPropertyNames(originalDate).forEach(prop => {
            if (typeof originalDate[prop] === 'function' && prop !== 'now') {
                window.Date[prop] = originalDate[prop];
            }
        });

        // 重写 getTime 方法
        Date.prototype.getTime = function() {
            const originalTime = originalGetTime.call(this);
            if (this.constructor === window.Date && arguments.length === 0) {
                return originalTime + offsetMs;
            }
            return originalTime;
        };
    }

    // 格式化日期时间为本地输入格式
    function formatDateTimeLocal(date) {
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const day = String(date.getDate()).padStart(2, '0');
        const hours = String(date.getHours()).padStart(2, '0');
        const minutes = String(date.getMinutes()).padStart(2, '0');
        return `${year}-${month}-${day}T${hours}:${minutes}`;
    }

    // 创建UI界面
    function createUI() {
        const config = loadConfig();

        // 主容器
        const container = document.createElement('div');
        container.id = 'time-modifier-ui';
        container.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 999999;
            font-family: Arial, sans-serif;
            font-size: 12px;
            background: #fff;
            border: 1px solid #ddd;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            min-width: 320px;
            transition: all 0.3s ease;
        `;

        // 标题栏
        const header = document.createElement('div');
        header.style.cssText = `
            background: #f5f5f5;
            padding: 8px 12px;
            border-bottom: 1px solid #ddd;
            border-radius: 7px 7px 0 0;
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            align-items: center;
            user-select: none;
        `;
        header.innerHTML = `
            <span style="font-weight: bold; color: #333;">[表情] 时间修改器</span>
            <span id="toggle-btn" style="cursor: pointer; font-size: 14px;">${config.uiCollapsed ? '▼' : '▲'}</span>
        `;

        // 内容区域
        const content = document.createElement('div');
        content.id = 'time-modifier-content';
        content.style.cssText = `
            padding: 12px;
            ${config.uiCollapsed ? 'display: none;' : ''}
        `;

        // 启用开关
        const enableRow = document.createElement('div');
        enableRow.style.cssText = 'margin-bottom: 12px; display: flex; align-items: center;';
        enableRow.innerHTML = `
            <label style="display: flex; align-items: center; cursor: pointer;">
                <input type="checkbox" id="enable-checkbox" ${config.enabled ? 'checked' : ''}
                       style="margin-right: 8px;">
                <span>启用时间修改</span>
            </label>
        `;

        // 模式选择
        const modeRow = document.createElement('div');
        modeRow.style.cssText = 'margin-bottom: 12px;';
        modeRow.innerHTML = `
            <div style="margin-bottom: 6px; color: #666; font-weight: bold;">修改模式:</div>
            <div style="display: flex; gap: 15px;">
                <label style="display: flex; align-items: center; cursor: pointer;">
                    <input type="radio" name="mode" value="offset" ${config.mode === 'offset' ? 'checked' : ''}
                           style="margin-right: 5px;">
                    <span>时间偏移</span>
                </label>
                <label style="display: flex; align-items: center; cursor: pointer;">
                    <input type="radio" name="mode" value="fixed" ${config.mode === 'fixed' ? 'checked' : ''}
                           style="margin-right: 5px;">
                    <span>固定时间</span>
                </label>
            </div>
        `;

        // 时间偏移设置
        const offsetRow = document.createElement('div');
        offsetRow.id = 'offset-settings';
        offsetRow.style.cssText = `margin-bottom: 12px; ${config.mode === 'fixed' ? 'display: none;' : ''}`;
        offsetRow.innerHTML = `
            <div style="margin-bottom: 6px; color: #666;">时间偏移:</div>
            <div style="display: flex; gap: 10px; align-items: center;">
                <input type="number" id="hours-input" value="${config.offsetHours}"
                       style="width: 60px; padding: 4px; border: 1px solid #ddd; border-radius: 4px;"
                       min="-23" max="23"> 小时
                <input type="number" id="minutes-input" value="${config.offsetMinutes}"
                       style="width: 60px; padding: 4px; border: 1px solid #ddd; border-radius: 4px;"
                       min="-59" max="59"> 分钟
            </div>
        `;

        // 固定时间设置
        const fixedRow = document.createElement('div');
        fixedRow.id = 'fixed-settings';
        fixedRow.style.cssText = `margin-bottom: 12px; ${config.mode === 'offset' ? 'display: none;' : ''}`;
        const currentTime = new Date();
        const defaultDateTime = config.fixedDateTime || formatDateTimeLocal(currentTime);
        fixedRow.innerHTML = `
            <div style="margin-bottom: 6px; color: #666;">固定时间:</div>
            <div style="display: flex; flex-direction: column; gap: 6px;">
                <input type="datetime-local" id="fixed-datetime-input" value="${defaultDateTime}"
                       style="padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 12px;">
                <div style="display: flex; gap: 5px;">
                    <button id="set-current-time" style="padding: 4px 8px; background: #17a2b8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">设为当前时间</button>
                    <button id="add-one-hour" style="padding: 4px 8px; background: #ffc107; color: black; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">+1小时</button>
                    <button id="add-one-day" style="padding: 4px 8px; background: #fd7e14; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">+1天</button>
                </div>
            </div>
        `;

        // 域名管理
        const domainRow = document.createElement('div');
        domainRow.style.cssText = 'margin-bottom: 12px;';
        domainRow.innerHTML = `
            <div style="margin-bottom: 6px; color: #666;">启用域名 (留空表示所有域名):</div>
            <div style="display: flex; gap: 5px; margin-bottom: 5px;">
                <input type="text" id="domain-input" placeholder="输入域名,如: example.com"
                       style="flex: 1; padding: 4px; border: 1px solid #ddd; border-radius: 4px; font-size: 11px;">
                <button id="add-domain-btn" style="padding: 4px 8px; background: #007cba; color: white; border: none; border-radius: 4px; cursor: pointer;">添加</button>
            </div>
            <div id="domain-list" style="max-height: 80px; overflow-y: auto;"></div>
        `;

        // 当前状态显示
        const statusRow = document.createElement('div');
        statusRow.style.cssText = 'margin-bottom: 12px; padding: 8px; background: #f9f9f9; border-radius: 4px; font-size: 11px;';
        statusRow.innerHTML = `
            <div><strong>当前域名:</strong> ${currentDomain}</div>
            <div><strong>系统时间:</strong> <span id="system-time">--</span></div>
            <div id="current-time"><strong>修改后时间:</strong> <span id="time-display">--</span></div>
            <div id="mode-status"><strong>当前模式:</strong> <span id="mode-display">${config.mode === 'offset' ? '时间偏移' : '固定时间'}</span></div>
        `;

        // 操作按钮
        const buttonRow = document.createElement('div');
        buttonRow.style.cssText = 'display: flex; gap: 8px; justify-content: flex-end;';
        buttonRow.innerHTML = `
            <button id="apply-btn" style="padding: 6px 12px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer;">应用</button>
            <button id="reset-btn" style="padding: 6px 12px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer;">重置</button>
        `;

        // 组装UI
        content.appendChild(enableRow);
        content.appendChild(modeRow);
        content.appendChild(offsetRow);
        content.appendChild(fixedRow);
        content.appendChild(domainRow);
        content.appendChild(statusRow);
        content.appendChild(buttonRow);

        container.appendChild(header);
        container.appendChild(content);

        return container;
    }

    // 更新域名列表显示
    function updateDomainList(container, config) {
        const domainList = container.querySelector('#domain-list');
        domainList.innerHTML = '';

        config.enabledDomains.forEach((domain, index) => {
            const domainItem = document.createElement('div');
            domainItem.style.cssText = 'display: flex; justify-content: space-between; align-items: center; padding: 2px 0; font-size: 11px;';
            domainItem.innerHTML = `
                <span>${domain}</span>
                <button data-index="${index}" class="remove-domain" style="background: #dc3545; color: white; border: none; border-radius: 2px; padding: 1px 4px; cursor: pointer; font-size: 10px;">×</button>
            `;
            domainList.appendChild(domainItem);
        });
    }

    // 更新时间显示
    function updateTimeDisplay(container) {
        const systemTimeDisplay = container.querySelector('#system-time');
        const timeDisplay = container.querySelector('#time-display');
        const modeDisplay = container.querySelector('#mode-display');

        if (systemTimeDisplay) {
            systemTimeDisplay.textContent = new Date().toLocaleString();
        }

        if (timeDisplay) {
            const config = loadConfig();
            if (config.enabled && isDomainEnabled(config)) {
                const offsetMs = calculateTimeOffset(config);
                const modifiedTime = new Date(Date.now() + offsetMs);
                timeDisplay.textContent = modifiedTime.toLocaleString();
                timeDisplay.style.color = '#28a745';
                timeDisplay.style.fontWeight = 'bold';
            } else {
                timeDisplay.textContent = '未启用';
                timeDisplay.style.color = '#6c757d';
                timeDisplay.style.fontWeight = 'normal';
            }
        }

        if (modeDisplay) {
            const config = loadConfig();
            modeDisplay.textContent = config.mode === 'offset' ? '时间偏移' : '固定时间';
        }
    }

    // 绑定事件
    function bindEvents(container) {
        const config = loadConfig();

        // 折叠/展开
        const toggleBtn = container.querySelector('#toggle-btn');
        const content = container.querySelector('#time-modifier-content');
        container.querySelector('div').addEventListener('click', (e) => {
            if (e.target === toggleBtn || e.target.parentElement === container.querySelector('div')) {
                const isCollapsed = content.style.display === 'none';
                content.style.display = isCollapsed ? 'block' : 'none';
                toggleBtn.textContent = isCollapsed ? '▲' : '▼';
                config.uiCollapsed = !isCollapsed;
                saveConfig(config);
            }
        });

        // 模式切换
        const modeRadios = container.querySelectorAll('input[name="mode"]');
        modeRadios.forEach(radio => {
            radio.addEventListener('change', () => {
                const offsetSettings = container.querySelector('#offset-settings');
                const fixedSettings = container.querySelector('#fixed-settings');

                if (radio.value === 'offset') {
                    offsetSettings.style.display = 'block';
                    fixedSettings.style.display = 'none';
                } else {
                    offsetSettings.style.display = 'none';
                    fixedSettings.style.display = 'block';
                }
                updateTimeDisplay(container);
            });
        });

        // 固定时间快捷按钮
        const setCurrentTimeBtn = container.querySelector('#set-current-time');
        const addOneHourBtn = container.querySelector('#add-one-hour');
        const addOneDayBtn = container.querySelector('#add-one-day');
        const fixedDatetimeInput = container.querySelector('#fixed-datetime-input');

        setCurrentTimeBtn.addEventListener('click', () => {
            fixedDatetimeInput.value = formatDateTimeLocal(new Date());
            updateTimeDisplay(container);
        });

        addOneHourBtn.addEventListener('click', () => {
            const currentValue = new Date(fixedDatetimeInput.value || new Date());
            currentValue.setHours(currentValue.getHours() + 1);
            fixedDatetimeInput.value = formatDateTimeLocal(currentValue);
            updateTimeDisplay(container);
        });

        addOneDayBtn.addEventListener('click', () => {
            const currentValue = new Date(fixedDatetimeInput.value || new Date());
            currentValue.setDate(currentValue.getDate() + 1);
            fixedDatetimeInput.value = formatDateTimeLocal(currentValue);
            updateTimeDisplay(container);
        });

        // 固定时间输入变化
        fixedDatetimeInput.addEventListener('change', () => {
            updateTimeDisplay(container);
        });

        // 偏移时间输入变化
        const hoursInput = container.querySelector('#hours-input');
        const minutesInput = container.querySelector('#minutes-input');
        [hoursInput, minutesInput].forEach(input => {
            input.addEventListener('input', () => {
                updateTimeDisplay(container);
            });
        });

        // 添加域名
        const addDomainBtn = container.querySelector('#add-domain-btn');
        const domainInput = container.querySelector('#domain-input');
        addDomainBtn.addEventListener('click', () => {
            const domain = domainInput.value.trim();
            if (domain && !config.enabledDomains.includes(domain)) {
                config.enabledDomains.push(domain);
                saveConfig(config);
                updateDomainList(container, config);
                domainInput.value = '';
            }
        });

        // 删除域名
        container.addEventListener('click', (e) => {
            if (e.target.classList.contains('remove-domain')) {
                const index = parseInt(e.target.dataset.index);
                config.enabledDomains.splice(index, 1);
                saveConfig(config);
                updateDomainList(container, config);
            }
        });

        // 应用设置
        const applyBtn = container.querySelector('#apply-btn');
        applyBtn.addEventListener('click', () => {
            const enabled = container.querySelector('#enable-checkbox').checked;
            const mode = container.querySelector('input[name="mode"]:checked').value;
            const hours = parseInt(container.querySelector('#hours-input').value) || 0;
            const minutes = parseInt(container.querySelector('#minutes-input').value) || 0;
            const fixedDateTime = container.querySelector('#fixed-datetime-input').value;

            config.enabled = enabled;
            config.mode = mode;
            config.offsetHours = hours;
            config.offsetMinutes = minutes;
            config.fixedDateTime = fixedDateTime;
            saveConfig(config);

            if (enabled && isDomainEnabled(config)) {
                const offsetMs = calculateTimeOffset(config);
                modifyTimeMethods(offsetMs);
            }

            alert('设置已应用!刷新页面生效。');
        });

        // 重置设置
        const resetBtn = container.querySelector('#reset-btn');
        resetBtn.addEventListener('click', () => {
            if (confirm('确定要重置所有设置吗?')) {
                localStorage.removeItem(STORAGE_KEY);
                location.reload();
            }
        });

        // 初始化域名列表
        updateDomainList(container, config);

        // 定时更新时间显示
        setInterval(() => updateTimeDisplay(container), 1000);
        updateTimeDisplay(container);
    }

    // 初始化
    function init() {
        const config = loadConfig();

        // 如果启用且当前域名在列表中,则修改时间
        if (config.enabled && isDomainEnabled(config)) {
            const offsetMs = calculateTimeOffset(config);
            modifyTimeMethods(offsetMs);
        }

        // 页面加载完成后创建UI
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                setTimeout(() => {
                    const ui = createUI();
                    document.body.appendChild(ui);
                    bindEvents(ui);
                }, 1000);
            });
        } else {
            setTimeout(() => {
                const ui = createUI();
                document.body.appendChild(ui);
                bindEvents(ui);
            }, 1000);
        }
    }

    // 启动脚本
    init();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址