您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
正确显示Twitter/X帖子的本地时间
// ==UserScript== // @name Twitter/X 时间格式化工具 // @namespace https://greasyfork.org/ // @version 2.6 // @license MIT // @description 正确显示Twitter/X帖子的本地时间 // @author Richard Tyson // @match https://twitter.com/* // @match https://mobile.twitter.com/* // @match https://x.com/* // @match https://mobile.x.com/* // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // ==/UserScript== (function() { 'use strict'; // 默认配置 const defaultConfig = { showSeconds: true, showTimezone: false, // 默认不显示时区标签 dateFormat: 'YYYY-MM-DD', timeFormat: '24' }; // 初始化配置 let config = { ...defaultConfig, ...JSON.parse(GM_getValue('timeFormatConfig', JSON.stringify(defaultConfig))) }; // 注册菜单命令 GM_registerMenuCommand('设置时间格式', showConfigDialog); // 添加样式 GM_addStyle(` #timeFormatConfigDialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: var(--background-primary, white); color: var(--text-primary, black); padding: 20px; border-radius: 8px; box-shadow: 0 0 20px rgba(0,0,0,0.5); z-index: 9999; width: 300px; font-family: Arial, sans-serif; border: 1px solid var(--border-color, #ddd); } #timeFormatConfigDialog h3 { margin-top: 0; color: var(--twitter-blue, #1da1f2); } .config-field { margin-bottom: 15px; } .config-label { display: block; margin-bottom: 5px; font-weight: bold; } .config-select { width: 100%; padding: 8px; border: 1px solid var(--border-color, #ddd); border-radius: 4px; } .config-checkbox { margin-right: 8px; } .config-buttons { margin-top: 20px; text-align: right; } .config-button { padding: 8px 15px; margin-left: 10px; border: none; border-radius: 4px; cursor: pointer; } .config-save { background-color: var(--twitter-blue, #1da1f2); color: white; } .config-close { background-color: var(--background-secondary, #ddd); } #timeFormatConfigOverlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9998; } `); // 显示配置对话框 function showConfigDialog() { const overlay = document.createElement('div'); overlay.id = 'timeFormatConfigOverlay'; const dialog = document.createElement('div'); dialog.id = 'timeFormatConfigDialog'; dialog.innerHTML = ` <h3>时间格式设置</h3> <div class="config-field"> <label class="config-label">日期格式</label> <select class="config-select" id="dateFormatSelect"> <option value="YYYY-MM-DD">年-月-日</option> <option value="DD-MM-YYYY">日-月-年</option> </select> </div> <div class="config-field"> <label class="config-label">时间格式</label> <select class="config-select" id="timeFormatSelect"> <option value="24">24小时制</option> <option value="12">12小时制</option> </select> </div> <div class="config-field"> <label> <input type="checkbox" class="config-checkbox" id="showSecondsCheckbox"> 显示秒数 </label> </div> <div class="config-field"> <label> <input type="checkbox" class="config-checkbox" id="showTimezoneCheckbox"> 显示时区标签 </label> </div> <div class="config-buttons"> <button class="config-button config-close" id="closeButton">关闭</button> <button class="config-button config-save" id="saveButton">保存</button> </div> `; // 设置当前值 dialog.querySelector('#dateFormatSelect').value = config.dateFormat; dialog.querySelector('#timeFormatSelect').value = config.timeFormat; dialog.querySelector('#showSecondsCheckbox').checked = config.showSeconds; dialog.querySelector('#showTimezoneCheckbox').checked = config.showTimezone; // 添加事件监听 dialog.querySelector('#closeButton').addEventListener('click', () => { document.body.removeChild(overlay); document.body.removeChild(dialog); }); dialog.querySelector('#saveButton').addEventListener('click', () => { const newConfig = { dateFormat: dialog.querySelector('#dateFormatSelect').value, timeFormat: dialog.querySelector('#timeFormatSelect').value, showSeconds: dialog.querySelector('#showSecondsCheckbox').checked, showTimezone: dialog.querySelector('#showTimezoneCheckbox').checked }; updateConfig(newConfig); document.body.removeChild(overlay); document.body.removeChild(dialog); }); document.body.appendChild(overlay); document.body.appendChild(dialog); } // 更新配置 function updateConfig(newConfig) { config = newConfig; GM_setValue('timeFormatConfig', JSON.stringify(config)); processedElements = new WeakSet(); processAllTimeElements(); } // 工具函数 const pad = (num) => String(num).padStart(2, '0'); let processedElements = new WeakSet(); // 获取系统时区 function getSystemTimezone() { const offset = -new Date().getTimezoneOffset(); const sign = offset >= 0 ? '+' : '-'; const hours = pad(Math.floor(Math.abs(offset) / 60)); const minutes = pad(Math.abs(offset) % 60); return `UTC${sign}${hours}:${minutes}`; } // 时间格式化 function formatDateTime(dateStr) { const date = new Date(dateStr); if (isNaN(date.getTime())) return "Invalid date"; // 日期部分 const year = date.getFullYear(); const month = pad(date.getMonth() + 1); const day = pad(date.getDate()); const formattedDate = config.dateFormat === 'DD-MM-YYYY' ? `${day}-${month}-${year}` : `${year}-${month}-${day}`; // 时间部分 let hours = date.getHours(); let ampm = ''; if (config.timeFormat === '12') { ampm = hours >= 12 ? ' PM' : ' AM'; hours = hours % 12 || 12; } const displayHours = pad(hours); const displayMinutes = pad(date.getMinutes()); const displaySeconds = config.showSeconds ? `:${pad(date.getSeconds())}` : ''; const formattedTime = `${displayHours}:${displayMinutes}${displaySeconds}${ampm}`; // 时区显示 const tzDisplay = config.showTimezone ? getSystemTimezone() : ''; return `${formattedDate} ${formattedTime} ${tzDisplay}`.trim(); } // 处理所有时间元素 function processAllTimeElements() { document.querySelectorAll('time[datetime]').forEach(timeElement => { processedElements.delete(timeElement); const datetimeStr = timeElement.getAttribute('datetime'); if (!datetimeStr) return; timeElement.textContent = formatDateTime(datetimeStr); processedElements.add(timeElement); }); } // 监听新内容 const observer = new MutationObserver(mutations => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && (node.matches('time[datetime]') || node.querySelector('time[datetime]'))) { processAllTimeElements(); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); processAllTimeElements(); })();