Greasy Fork is available in English.
修改浏览器JS获取的本地时间,支持偏移和固定时间设置
当前为
// ==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();
})();