// ==UserScript==
// @name 时间修改器(增强)
// @namespace http://tampermonkey.net/
// @version 4.0
// @description 修改浏览器JS获取的本地时间,支持偏移和固定时间设置
// @author Vap
// @match *://*/*
// @grant none
// @run-at document-start
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/537424/%E6%97%B6%E9%97%B4%E4%BF%AE%E6%94%B9%E5%99%A8%EF%BC%88%E5%A2%9E%E5%BC%BA%EF%BC%89.user.js
// @updateURL https://update.greasyfork.icu/scripts/537424/%E6%97%B6%E9%97%B4%E4%BF%AE%E6%94%B9%E5%99%A8%EF%BC%88%E5%A2%9E%E5%BC%BA%EF%BC%89.meta.js
// ==/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,
position: { x: null, y: null }, // 新增:保存UI位置
};
// 获取当前域名
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';
// 基础样式
const baseStyles = `
position: fixed;
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);
transition: all 0.3s ease;
`;
// 展开状态样式
const expandedStyles = `
top: 20px;
right: 20px;
min-width: 320px;
`;
// 收起状态样式
const collapsedStyles = `
bottom: 20px;
right: 20px;
min-width: auto;
cursor: move;
`;
// 应用初始样式
container.style.cssText =
baseStyles + (config.uiCollapsed ? collapsedStyles : expandedStyles);
// 如果有保存的位置且UI是收起状态,应用保存的位置
if (
config.uiCollapsed &&
config.position.x !== null &&
config.position.y !== null
) {
container.style.right = 'auto';
container.style.bottom = 'auto';
container.style.left = `${config.position.x}px`;
container.style.top = `${config.position.y}px`;
}
// 标题栏
const header = document.createElement('div');
header.style.cssText = `
background: #f5f5f5;
padding: 8px 12px;
border-bottom: ${config.uiCollapsed ? 'none' : '1px solid #ddd'};
border-radius: 7px ${config.uiCollapsed ? '7px 7px' : '7px 0 0'};
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
`;
header.innerHTML = `
${
config.uiCollapsed ? '⏰' : '⏰ 时间修改器'
}
${
config.uiCollapsed ? '▼' : '▲'
}
`;
// 内容区域
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 = `
`;
// 模式选择
const modeRow = document.createElement('div');
modeRow.style.cssText = 'margin-bottom: 12px;';
modeRow.innerHTML = `
修改模式:
`;
// 时间偏移设置
const offsetRow = document.createElement('div');
offsetRow.id = 'offset-settings';
offsetRow.style.cssText = `margin-bottom: 12px; ${
config.mode === 'fixed' ? 'display: none;' : ''
}`;
offsetRow.innerHTML = `
时间偏移:
小时
分钟
`;
// 固定时间设置
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 = `
固定时间:
`;
// 域名管理
const domainRow = document.createElement('div');
domainRow.style.cssText = 'margin-bottom: 12px;';
domainRow.innerHTML = `
启用域名 (留空表示所有域名):
`;
// 当前状态显示
const statusRow = document.createElement('div');
statusRow.style.cssText =
'margin-bottom: 12px; padding: 8px; background: #f9f9f9; border-radius: 4px; font-size: 11px;';
statusRow.innerHTML = `
当前域名: ${currentDomain}
系统时间: --
修改后时间: --
当前模式: ${
config.mode === 'offset' ? '时间偏移' : '固定时间'
}
`;
// 操作按钮
const buttonRow = document.createElement('div');
buttonRow.style.cssText =
'display: flex; gap: 8px; justify-content: flex-end;';
buttonRow.innerHTML = `
`;
// 组装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 = `
${domain}
`;
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 makeDraggable(element) {
let isDragging = false;
let offsetX, offsetY;
// 鼠标按下事件
element.addEventListener('mousedown', function (e) {
// 只有在收起状态且点击标题栏时才能拖动
const config = loadConfig();
if (!config.uiCollapsed) return;
// 确保点击的是标题栏
const header = element.querySelector('div');
if (e.target !== header && !header.contains(e.target)) return;
isDragging = true;
// 计算鼠标在元素内的偏移量
const rect = element.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
// 防止拖动时选中文本
e.preventDefault();
});
// 鼠标移动事件
document.addEventListener('mousemove', function (e) {
if (!isDragging) return;
// 计算新位置
const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
// 限制在窗口内
const maxX = window.innerWidth - element.offsetWidth;
const maxY = window.innerHeight - element.offsetHeight;
const newX = Math.max(0, Math.min(x, maxX));
const newY = Math.max(0, Math.min(y, maxY));
// 应用新位置
element.style.left = `${newX}px`;
element.style.top = `${newY}px`;
element.style.right = 'auto';
element.style.bottom = 'auto';
// 保存位置到配置
const config = loadConfig();
config.position = { x: newX, y: newY };
saveConfig(config);
});
// 鼠标释放事件
document.addEventListener('mouseup', function () {
isDragging = false;
});
// 鼠标离开窗口事件
document.addEventListener('mouseleave', function () {
isDragging = false;
});
}
// 绑定事件
function bindEvents(container) {
const config = loadConfig();
// 使容器可拖动
makeDraggable(container);
// 折叠/展开
const toggleBtn = container.querySelector('#toggle-btn');
const content = container.querySelector('#time-modifier-content');
const header = container.querySelector('div');
header.addEventListener('click', e => {
// 如果正在拖动,不触发折叠/展开
if (container.isDragging) return;
if (e.target === toggleBtn || e.target.parentElement === header) {
const isCollapsed = content.style.display === 'none';
content.style.display = isCollapsed ? 'block' : 'none';
toggleBtn.textContent = isCollapsed ? '▲' : '▼';
// 更新配置
config.uiCollapsed = !isCollapsed;
saveConfig(config);
// 更新UI样式
if (isCollapsed) {
// 展开状态
container.style.minWidth = '320px';
container.style.top = '20px';
container.style.right = '20px';
container.style.left = 'auto';
container.style.bottom = 'auto';
container.style.cursor = 'default';
header.style.borderBottom = '1px solid #ddd';
header.style.borderRadius = '7px 7px 0 0';
header.querySelector('span').textContent = '⏰ 时间修改器';
} else {
// 收起状态
container.style.minWidth = 'auto';
container.style.cursor = 'move';
header.style.borderBottom = 'none';
header.style.borderRadius = '7px';
header.querySelector('span').textContent = '⏰';
// 如果有保存的位置,使用保存的位置
if (config.position.x !== null && config.position.y !== null) {
container.style.left = `${config.position.x}px`;
container.style.top = `${config.position.y}px`;
container.style.right = 'auto';
container.style.bottom = 'auto';
} else {
// 否则默认放在右下角
container.style.bottom = '20px';
container.style.right = '20px';
container.style.top = 'auto';
container.style.left = 'auto';
}
}
}
});
// 模式切换
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();
})();