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 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Displays Date & Times within Anthropic Claude Conversations (ES5-safe)
// @namespace    http://tampermonkey.net/
// @version      2.5
// @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: 15 // increased from 12px (~+2pt)
  };

  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);

  messageElement.insertBefore(timestampElement, messageElement.firstChild);
}

  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);
  }
})();


QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址