🏠 Home 

Stage1 Local Time Replacer

Replace and overwrite ##### Standard Time with local time on Stage1 forums.


Install this script?
  1. // ==UserScript==
  2. // @name Stage1 Local Time Replacer
  3. // @name:zh-CN Stage1本地时间替换
  4. // @namespace user-NITOUCHE
  5. // @version 1.3.2
  6. // @description Replace and overwrite ##### Standard Time with local time on Stage1 forums.
  7. // @description:zh-CN 用本地时间替换覆盖Stage1论坛中的##时间。
  8. // @author DS泥头车
  9. // @match https://*.saraba1st.com/2b/*
  10. // @icon https://bbs.saraba1st.com/favicon.ico
  11. // @grant GM_addStyle
  12. // @license MIT
  13. // @run-at document-end
  14. // ==/UserScript==
  15. (function() {
  16. 'use strict';
  17. // 添加 CSS 样式到页面
  18. // 使用 GM_addStyle 是为了避免与页面原有 CSS 冲突,并确保样式能被正确应用
  19. GM_addStyle(`
  20. .s1-local-time {
  21. font: inherit !important; /* 继承父元素的字体样式,!important 确保覆盖原有样式 */
  22. }
  23. .s1-local-time.blue-replaced {
  24. color: #000000 !important; /* 替换为蓝色时的时间颜色,!important 确保覆盖原有样式 */
  25. }
  26. .s1-local-time.orange-replaced {
  27. color: #F26C4F !important; /* 替换为橙色时的时间颜色,!important 确保覆盖原有样式 */
  28. }
  29. `);
  30. let isProcessing = false; // 标志变量,防止重复处理,避免 MutationObserver 触发多次处理
  31. // 获取元素的颜色值 (RGB 格式)
  32. function getElementColor(el) {
  33. const color = window.getComputedStyle(el).color; // 获取计算后的颜色值
  34. const rgb = color.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/i); // 使用正则表达式匹配 RGB 格式
  35. if (rgb) {
  36. // 将 RGB 转换为十六进制颜色值 (方便比较)
  37. return (parseInt(rgb[1]) << 16) | (parseInt(rgb[2]) << 8) | parseInt(rgb[3]);
  38. }
  39. return null; // 如果颜色格式不是 RGB,则返回 null
  40. }
  41. // 将北#时间转换为本地时间
  42. function convertBeijingToLocal(beijingTime) {
  43. try {
  44. // 创建 Date 对象,并指定时区为 UTC+8 (北#时间)
  45. const date = new Date(beijingTime + '+08:00');
  46. // 使用 toLocaleString 格式化为本地时间,并指定中文格式
  47. return date.toLocaleString('zh-CN', {
  48. year: 'numeric', // 年份:四位数字
  49. month: 'numeric', // 月份:数字
  50. day: 'numeric', // 日期:数字
  51. hour: '2-digit', // 小时:两位数字 (24小时制)
  52. minute: '2-digit',// 分钟:两位数字
  53. hour12: false // 禁用 12 小时制
  54. }).replace(/(\d+)\/(\d+)\/(\d+)/, '$1-$2-$3'); // 将日期格式中的斜杠替换为短横线,例如:2023/10/26 -> 2023-10-26
  55. } catch(e) {
  56. // 如果转换出错 (例如,时间格式不正确),则返回原始北#时间
  57. return beijingTime;
  58. }
  59. }
  60. // 处理单个元素及其子元素中的时间字符串
  61. function processElement(el) {
  62. if (el.dataset.timeReplaced || el.querySelector('[data-time-replaced]')) return; // 如果元素或其子元素已经被处理过,则跳过,避免重复处理
  63. let processed = false; // 标记是否在该元素中找到了并处理了时间
  64. const timeRegex = /(\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2})/; // 匹配 "YYYY-MM-DD HH:MM" 格式的时间字符串的正则表达式
  65. const treeWalker = document.createTreeWalker(
  66. el, // 从当前元素开始遍历
  67. NodeFilter.SHOW_TEXT, // 只遍历文本节点
  68. null, // 无需自定义过滤器
  69. false // 不需要实体扩展
  70. );
  71. let textNode;
  72. while (textNode = treeWalker.nextNode()) { // 遍历文本节点
  73. if (textNode.textContent.trim() && timeRegex.test(textNode.textContent)) { // 如果文本节点不为空白,并且包含匹配时间格式的字符串
  74. const match = textNode.textContent.match(timeRegex); // 匹配时间字符串
  75. if (!match) continue; // 如果没有匹配到,则继续下一个文本节点
  76. const originalColor = getElementColor(textNode.parentElement); // 获取时间字符串父元素的颜色,用于判断时间颜色类型
  77. let colorClass = ''; // 初始化颜色 CSS 类名
  78. if (originalColor === 0xF26C4F) { // 橙色 (0xF26C4F 是橙色的十六进制 RGB 值)
  79. colorClass = 'orange-replaced';
  80. } else if (originalColor === 0x022C80 || originalColor === 0x22c || originalColor === 0x999999) { // 蓝色 (0x022C80, 0x22c, 0x999999 可能是不同深浅的蓝色或灰色)
  81. colorClass = 'blue-replaced';
  82. }
  83. const timeSpan = document.createElement('span'); // 创建 span 元素用于包裹转换后的本地时间
  84. timeSpan.className = `s1-local-time ${colorClass}`.trim(); // 设置 span 的 class,应用样式
  85. timeSpan.textContent = convertBeijingToLocal(match[0]); // 将北#时间转换为本地时间并设置为 span 的文本内容
  86. timeSpan.dataset.timeReplaced = "true"; // 标记该 span 已经被时间替换过
  87. const beforeTimeText = document.createTextNode(textNode.textContent.substring(0, match.index)); // 创建时间字符串前面的文本节点
  88. const afterTimeText = document.createTextNode(textNode.textContent.substring(match.index + match[0].length)); // 创建时间字符串后面的文本节点
  89. const parentNode = textNode.parentNode; // 获取文本节点的父元素
  90. parentNode.replaceChild(timeSpan, textNode); // 将原来的文本节点替换为 span 元素
  91. if (afterTimeText.textContent) { // 如果时间字符串后面还有文本
  92. timeSpan.parentNode.insertBefore(afterTimeText, timeSpan.nextSibling); // 将后面的文本节点插入到 span 后面
  93. }
  94. if (beforeTimeText.textContent) { // 如果时间字符串前面还有文本
  95. timeSpan.parentNode.insertBefore(beforeTimeText, timeSpan); // 将前面的文本节点插入到 span 前面
  96. }
  97. processed = true; // 标记在该元素中找到了并处理了时间
  98. break; // 找到并处理一个时间后,跳出当前文本节点的遍历,处理下一个文本节点 (TreeWalker 会自动继续遍历)
  99. }
  100. }
  101. if (processed) {
  102. el.dataset.timeReplaced = "true"; // 标记该元素已经被处理过,即使只替换了一个时间,也避免重复处理
  103. }
  104. }
  105. // 处理页面中所有符合选择器条件的元素
  106. function processAll() {
  107. if (isProcessing) return; // 如果正在处理中,则直接返回,避免重复处理
  108. isProcessing = true; // 设置处理中标志
  109. document.querySelectorAll(`
  110. em[id^="authorposton"], /* Discuz! 帖子发布时间 */
  111. i.pstatus, /* Discuz! 可能的状态时间 */
  112. cite, /* 引用内容的时间 */
  113. td.by em span, /* Discuz! 回复时间 */
  114. a[href*="forum.php?mod=redirect"], /* Discuz! 跳转链接中的时间 */
  115. div.quote font, /* Discuz! 引用块中的时间 (旧版) */
  116. div.blockquote font, /* Discuz! 引用块中的时间 (新版) */
  117. blockquote font, /* 通用引用块中的时间 */
  118. a[href*="forum.php?mod=misc"], /* Discuz! 其他链接中的时间 */
  119. ul#pbbs li, /* Discuz! 瀑布流帖子列表时间 */
  120. table td, /* 通用表格单元格,可能包含时间 */
  121. span.xg1.xw0, /* Discuz! 一些辅助信息的时间 */
  122. p span, /* 段落中的 span,可能包含时间 */
  123. li.bbda span.xg1 /* Discuz! 列表项辅助信息时间 */
  124. `).forEach(processElement); // 遍历选择器匹配到的所有元素,并调用 processElement 函数进行处理
  125. isProcessing = false; // 清除处理中标志
  126. }
  127. processAll(); // 页面加载时立即执行一次,处理页面上已有的时间
  128. // 创建 MutationObserver 监听 DOM 变化
  129. new MutationObserver(mutations => {
  130. mutations.forEach(mut => { // 遍历每个 mutation 记录
  131. if (mut.type === 'childList') { // 如果 mutation 类型是子节点列表变化 (即有节点被添加或移除)
  132. mut.addedNodes.forEach(node => { // 遍历所有被添加的节点
  133. if (node.nodeType === Node.ELEMENT_NODE) { // 如果添加的节点是元素节点
  134. processAll(); // 重新处理所有符合条件的元素,包括新添加的元素
  135. }
  136. });
  137. }
  138. });
  139. }).observe(document.body, { // 监听 document.body 及其子树的 DOM 变化
  140. childList: true, // 监听子节点列表变化 (添加或移除节点)
  141. subtree: true, // 监听整个子树的变化,包括后代节点
  142. attributes: false // 不监听属性变化
  143. });
  144. })();