🏠 Home 

Greasy Fork is available in English.

Stage1本地时间替换

用本地时间替换覆盖Stage1论坛中的##时间。


安装此脚本?
// ==UserScript==
// @name         Stage1 Local Time Replacer
// @name:zh-CN   Stage1本地时间替换
// @namespace    user-NITOUCHE
// @version      1.3.2
// @description  Replace and overwrite ##### Standard Time with local time on Stage1 forums.
// @description:zh-CN 用本地时间替换覆盖Stage1论坛中的##时间。
// @author       DS泥头车
// @match        https://*.saraba1st.com/2b/*
// @icon         https://bbs.saraba1st.com/favicon.ico
// @grant        GM_addStyle
// @license      MIT
// @run-at       document-end
// ==/UserScript==
(function() {
'use strict';
// 添加 CSS 样式到页面
// 使用 GM_addStyle 是为了避免与页面原有 CSS 冲突,并确保样式能被正确应用
GM_addStyle(`
.s1-local-time {
font: inherit !important; /* 继承父元素的字体样式,!important 确保覆盖原有样式 */
}
.s1-local-time.blue-replaced {
color: #000000 !important; /* 替换为蓝色时的时间颜色,!important 确保覆盖原有样式 */
}
.s1-local-time.orange-replaced {
color: #F26C4F !important; /* 替换为橙色时的时间颜色,!important 确保覆盖原有样式 */
}
`);
let isProcessing = false; // 标志变量,防止重复处理,避免 MutationObserver 触发多次处理
// 获取元素的颜色值 (RGB 格式)
function getElementColor(el) {
const color = window.getComputedStyle(el).color; // 获取计算后的颜色值
const rgb = color.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/i); // 使用正则表达式匹配 RGB 格式
if (rgb) {
// 将 RGB 转换为十六进制颜色值 (方便比较)
return (parseInt(rgb[1]) << 16) | (parseInt(rgb[2]) << 8) | parseInt(rgb[3]);
}
return null; // 如果颜色格式不是 RGB,则返回 null
}
// 将北#时间转换为本地时间
function convertBeijingToLocal(beijingTime) {
try {
// 创建 Date 对象,并指定时区为 UTC+8 (北#时间)
const date = new Date(beijingTime + '+08:00');
// 使用 toLocaleString 格式化为本地时间,并指定中文格式
return date.toLocaleString('zh-CN', {
year: 'numeric', // 年份:四位数字
month: 'numeric', // 月份:数字
day: 'numeric',   // 日期:数字
hour: '2-digit',  // 小时:两位数字 (24小时制)
minute: '2-digit',// 分钟:两位数字
hour12: false     // 禁用 12 小时制
}).replace(/(\d+)\/(\d+)\/(\d+)/, '$1-$2-$3'); // 将日期格式中的斜杠替换为短横线,例如:2023/10/26 -> 2023-10-26
} catch(e) {
// 如果转换出错 (例如,时间格式不正确),则返回原始北#时间
return beijingTime;
}
}
// 处理单个元素及其子元素中的时间字符串
function processElement(el) {
if (el.dataset.timeReplaced || el.querySelector('[data-time-replaced]')) return; // 如果元素或其子元素已经被处理过,则跳过,避免重复处理
let processed = false; // 标记是否在该元素中找到了并处理了时间
const timeRegex = /(\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2})/; // 匹配 "YYYY-MM-DD HH:MM" 格式的时间字符串的正则表达式
const treeWalker = document.createTreeWalker(
el,                                  // 从当前元素开始遍历
NodeFilter.SHOW_TEXT,                // 只遍历文本节点
null,                                // 无需自定义过滤器
false                                // 不需要实体扩展
);
let textNode;
while (textNode = treeWalker.nextNode()) { // 遍历文本节点
if (textNode.textContent.trim() && timeRegex.test(textNode.textContent)) { // 如果文本节点不为空白,并且包含匹配时间格式的字符串
const match = textNode.textContent.match(timeRegex); // 匹配时间字符串
if (!match) continue; // 如果没有匹配到,则继续下一个文本节点
const originalColor = getElementColor(textNode.parentElement); // 获取时间字符串父元素的颜色,用于判断时间颜色类型
let colorClass = ''; // 初始化颜色 CSS 类名
if (originalColor === 0xF26C4F) { // 橙色 (0xF26C4F 是橙色的十六进制 RGB 值)
colorClass = 'orange-replaced';
} else if (originalColor === 0x022C80 || originalColor === 0x22c || originalColor === 0x999999) { // 蓝色 (0x022C80, 0x22c, 0x999999 可能是不同深浅的蓝色或灰色)
colorClass = 'blue-replaced';
}
const timeSpan = document.createElement('span'); // 创建 span 元素用于包裹转换后的本地时间
timeSpan.className = `s1-local-time ${colorClass}`.trim(); // 设置 span 的 class,应用样式
timeSpan.textContent = convertBeijingToLocal(match[0]); // 将北#时间转换为本地时间并设置为 span 的文本内容
timeSpan.dataset.timeReplaced = "true"; // 标记该 span 已经被时间替换过
const beforeTimeText = document.createTextNode(textNode.textContent.substring(0, match.index)); // 创建时间字符串前面的文本节点
const afterTimeText = document.createTextNode(textNode.textContent.substring(match.index + match[0].length)); // 创建时间字符串后面的文本节点
const parentNode = textNode.parentNode; // 获取文本节点的父元素
parentNode.replaceChild(timeSpan, textNode); // 将原来的文本节点替换为 span 元素
if (afterTimeText.textContent) { // 如果时间字符串后面还有文本
timeSpan.parentNode.insertBefore(afterTimeText, timeSpan.nextSibling); // 将后面的文本节点插入到 span 后面
}
if (beforeTimeText.textContent) { // 如果时间字符串前面还有文本
timeSpan.parentNode.insertBefore(beforeTimeText, timeSpan); // 将前面的文本节点插入到 span 前面
}
processed = true; // 标记在该元素中找到了并处理了时间
break; // 找到并处理一个时间后,跳出当前文本节点的遍历,处理下一个文本节点 (TreeWalker 会自动继续遍历)
}
}
if (processed) {
el.dataset.timeReplaced = "true"; // 标记该元素已经被处理过,即使只替换了一个时间,也避免重复处理
}
}
// 处理页面中所有符合选择器条件的元素
function processAll() {
if (isProcessing) return; // 如果正在处理中,则直接返回,避免重复处理
isProcessing = true; // 设置处理中标志
document.querySelectorAll(`
em[id^="authorposton"],   /* Discuz! 帖子发布时间 */
i.pstatus,                /* Discuz! 可能的状态时间 */
cite,                     /* 引用内容的时间 */
td.by em span,             /* Discuz! 回复时间 */
a[href*="forum.php?mod=redirect"], /* Discuz! 跳转链接中的时间 */
div.quote font,            /* Discuz! 引用块中的时间 (旧版) */
div.blockquote font,       /* Discuz! 引用块中的时间 (新版) */
blockquote font,          /* 通用引用块中的时间 */
a[href*="forum.php?mod=misc"], /* Discuz! 其他链接中的时间 */
ul#pbbs li,               /* Discuz! 瀑布流帖子列表时间 */
table td,                 /* 通用表格单元格,可能包含时间 */
span.xg1.xw0,             /* Discuz! 一些辅助信息的时间 */
p span,                   /* 段落中的 span,可能包含时间 */
li.bbda span.xg1          /* Discuz! 列表项辅助信息时间 */
`).forEach(processElement); // 遍历选择器匹配到的所有元素,并调用 processElement 函数进行处理
isProcessing = false; // 清除处理中标志
}
processAll(); // 页面加载时立即执行一次,处理页面上已有的时间
// 创建 MutationObserver 监听 DOM 变化
new MutationObserver(mutations => {
mutations.forEach(mut => { // 遍历每个 mutation 记录
if (mut.type === 'childList') { // 如果 mutation 类型是子节点列表变化 (即有节点被添加或移除)
mut.addedNodes.forEach(node => { // 遍历所有被添加的节点
if (node.nodeType === Node.ELEMENT_NODE) { // 如果添加的节点是元素节点
processAll(); // 重新处理所有符合条件的元素,包括新添加的元素
}
});
}
});
}).observe(document.body, { // 监听 document.body 及其子树的 DOM 变化
childList: true,     // 监听子节点列表变化 (添加或移除节点)
subtree: true,       // 监听整个子树的变化,包括后代节点
attributes: false    // 不监听属性变化
});
})();