Replace and overwrite ##### Standard Time with local time on Stage1 forums.
- // ==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 // 不监听属性变化
- });
- })();