Displays Date & Times within Anthropic Claude Conversations (ES5-safe)

Reveal hidden timestamps in Claude conversations with robust DOM/API handling, XHR support, and debounced observer; always show year; larger font.

目前為 2025-08-14 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Displays Date & Times within Anthropic Claude Conversations (ES5-safe)
// @namespace    http://tampermonkey.net/
// @version      2.3
// @license      MIT
// @description  Reveal hidden timestamps in Claude conversations with robust DOM/API handling, XHR support, and debounced observer; always show year; larger font.
// @author       Wayne
// @match        https://claude.ai/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
  'use strict';

  var CONFIG = {
    timestampClass: 'claude-timestamp',
    messageSelector: "[data-testid*='message'], [class*='message'], .font-claude-message, [role='article']",
    observerDelay: 500,
    debounceDelay: 250,
    timestampFormat: 'absolute',
    position: 'inline',
    maxJsonScriptSize: 200000, // no numeric separator
    timestampFontSizePx: 16 // increased from 12px (~+3pt)
  };

  function formatTimestamp(isoString) {
    var date = new Date(isoString);
    return date.toLocaleString('en-US', {
      month: 'short',
      day: 'numeric',
      hour: 'numeric',
      minute: '2-digit',
      hour12: true,
      year: 'numeric'
    });
  }

  function injectStyles() {
    if (document.getElementById('claude-timestamp-styles')) return;
    var style = document.createElement('style');
    style.id = 'claude-timestamp-styles';
    style.textContent =
      '.' + CONFIG.timestampClass + ' {' +
      'font-size: ' + CONFIG.timestampFontSizePx + 'px;' +
      'color: #5d6269;' +
      'margin-bottom: 6px;' +
      "font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;" +
      'display: block;' +
      '}' +
      '@media (prefers-color-scheme: dark) {' +
      '.' + CONFIG.timestampClass + ' { color: #a2acba; }' +
      '}';
    document.head.appendChild(style);
  }

  function extractTimestamp(messageElement) {
    var dataAttrs = ['data-timestamp', 'data-created-at', 'data-time'];
    for (var i = 0; i < dataAttrs.length; i++) {
      var value = messageElement.getAttribute(dataAttrs[i]);
      if (value) return value;
    }

    var parent = messageElement.closest("[data-testid*='conversation'], .conversation, main");
    if (parent) {
      var scripts = parent.querySelectorAll("script[type='application/json']");
      for (var j = 0; j < scripts.length; j++) {
        var txt = scripts[j].textContent || '';
        if (txt.length > CONFIG.maxJsonScriptSize) continue;
        try {
          var data = JSON.parse(txt);
          if (data.created_at) return data.created_at;
          if (data.timestamp) return data.timestamp;
        } catch (e) {}
      }
    }

    var messageId = messageElement.id || messageElement.getAttribute('data-message-id');
    if (messageId && window.conversationData && window.conversationData.messages) {
      var message = null;
      for (var m = 0; m < window.conversationData.messages.length; m++) {
        if (window.conversationData.messages[m].id === messageId) {
          message = window.conversationData.messages[m];
          break;
        }
      }
      if (message && message.created_at) return message.created_at;
    }

    var keys = Object.keys(messageElement);
    var reactKey = null;
    for (var k = 0; k < keys.length; k++) {
      if (keys[k].indexOf('__reactFiber$') === 0 || keys[k].indexOf('__reactProps$') === 0 || keys[k].indexOf('__reactInternalInstance') === 0) {
        reactKey = keys[k];
        break;
      }
    }
    if (reactKey) {
      try {
        var fiber = messageElement[reactKey];
        var props = (fiber && fiber.return && fiber.return.memoizedProps) ||
                    (fiber && fiber.memoizedProps) ||
                    (fiber && fiber.pendingProps) ||
                    (fiber && fiber.return && fiber.return.pendingProps);
        var ts = (props && props.message && props.message.created_at) ||
                 (props && props.timestamp) ||
                 (props && props.createdAt);
        if (ts) return ts;
      } catch (e) {}
    }

    return null;
  }

  function interceptNetworkData() {
    var originalFetch = window.fetch;
    window.fetch = function () {
      var args = arguments;
      var req = args[0];
      var url = typeof req === 'string' ? req : (req && req.url ? req.url : '');
      return originalFetch.apply(this, args).then(function (res) {
        if (url && /(\/conversations|\/chat)\b/.test(url)) {
          try {
            var clone = res.clone();
            var ct = (clone.headers && typeof clone.headers.get === 'function' ? clone.headers.get('content-type') : '') || '';
            if (ct.indexOf('application/json') !== -1) {
              clone.json().then(function (data) {
                if (data && (data.messages || data.conversation)) {
                  window.conversationData = data;
                  setTimeout(processMessages, 100);
                }
              })["catch"](function () {});
            }
          } catch (e) {}
        }
        return res;
      });
    };
  }

  function interceptXHR() {
    var originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function (method, url) {
      this._url = url;
      return originalOpen.apply(this, arguments);
    };
    var originalSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function () {
      this.addEventListener('load', function () {
        if (this._url && /(\/conversations|\/chat)\b/.test(this._url)) {
          try {
            var ct = this.getResponseHeader('content-type') || '';
            if (ct.indexOf('application/json') !== -1) {
              var data = JSON.parse(this.responseText);
              if (data && (data.messages || data.conversation)) {
                window.conversationData = data;
                setTimeout(processMessages, 100);
              }
            }
          } catch (e) {}
        }
      });
      return originalSend.apply(this, arguments);
    };
  }

  function addTimestamp(messageElement, timestamp) {
    if (messageElement.querySelector('.' + CONFIG.timestampClass)) return;
    var timestampElement = document.createElement('div');
    timestampElement.className = CONFIG.timestampClass;
    timestampElement.textContent = formatTimestamp(timestamp);
    try { timestampElement.title = new Date(timestamp).toISOString(); } catch (e) {}
    messageElement.insertBefore(timestampElement, messageElement.firstChild);
  }

  var processing = false;
  function processMessages() {
    if (processing) return;
    processing = true;
    try {
      var messages = document.querySelectorAll(CONFIG.messageSelector);
      for (var i = 0; i < messages.length; i++) {
        var message = messages[i];
        if (message.querySelector('.' + CONFIG.timestampClass)) continue;
        var timestamp = extractTimestamp(message);
        if (!timestamp) {
          var estimatedTime = new Date(Date.now() - (messages.length - i - 1) * 30000);
          timestamp = estimatedTime.toISOString();
        }
        if (timestamp) addTimestamp(message, timestamp);
      }
    } finally {
      processing = false;
    }
  }

  function debounce(fn, delay) {
    var timer;
    return function () {
      var context = this, args = arguments;
      clearTimeout(timer);
      timer = setTimeout(function () {
        fn.apply(context, args);
      }, delay);
    };
  }

  function setupObserver() {
    var debouncedProcess = debounce(processMessages, CONFIG.debounceDelay);
    var observer = new MutationObserver(function (mutations) {
      var shouldProcess = false;
      for (var i = 0; i < mutations.length; i++) {
        var mutation = mutations[i];
        if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
          for (var n = 0; n < mutation.addedNodes.length; n++) {
            var node = mutation.addedNodes[n];
            if (node.nodeType === 1 && ( (node.matches && node.matches(CONFIG.messageSelector)) ||
                 (node.querySelector && node.querySelector(CONFIG.messageSelector)) )) {
              shouldProcess = true;
              break;
            }
          }
        }
        if (shouldProcess) break;
      }
      if (shouldProcess) debouncedProcess();
    });
    observer.observe(document.body, { childList: true, subtree: true });
    return observer;
  }

  function init() {
    injectStyles();
    interceptNetworkData();
    interceptXHR();
    setTimeout(processMessages, 1000);
    setupObserver();
    setInterval(processMessages, 10000);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    setTimeout(init, 100);
  }
})();