您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show timestamps for Claude conversation messages
// ==UserScript== // @name Claude Timestamp Revealer // @namespace http://tampermonkey.net/ // @version 3.0 // @license MIT // @description Show timestamps for Claude conversation messages // @author Baseline Claude Sonnet 4, enhanced by Baseline ChatGPT-5, debugged by Wayne L. "Grasshopper" Pendley's "Syntactico" persona, who is sourced from ChatGPT-4o // @match https://claude.ai/* // @grant none // ==/UserScript== (function() { 'use strict'; const CONFIG = { messageSelector: '.group\\/conversation-turn, .font-claude-response, [data-testid="user-message"]', timestampClass: 'claude-timestamp', storageKey: 'claudeTimestamps', observerDelay: 750 }; let timestampData = new Map(); // Load existing timestamp data function loadTimestampData() { try { const stored = localStorage.getItem(CONFIG.storageKey); if (stored) { const parsed = JSON.parse(stored); timestampData = new Map(Object.entries(parsed)); } } catch (e) { console.warn('Failed to load timestamp data:', e); } } // Save timestamp data function saveTimestampData() { try { const obj = Object.fromEntries(timestampData); localStorage.setItem(CONFIG.storageKey, JSON.stringify(obj)); } catch (e) { console.warn('Failed to save timestamp data:', e); } } // Inject CSS styles with !important to override Claude's styles function injectStyles() { const style = document.createElement('style'); style.textContent = ` .${CONFIG.timestampClass} { font-size: 15px !important; color: #555 !important; opacity: 1 !important; margin-bottom: 8px !important; margin-top: 4px !important; font-family: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace !important; display: block !important; line-height: 1.4 !important; } `; document.head.appendChild(style); } // Create timestamp element function createTimestamp(date) { const timestamp = document.createElement('div'); timestamp.className = CONFIG.timestampClass; const year = date.getUTCFullYear(); const month = String(date.getUTCMonth() + 1).padStart(2, '0'); const day = String(date.getUTCDate()).padStart(2, '0'); const hours = String(date.getUTCHours()).padStart(2, '0'); const minutes = String(date.getUTCMinutes()).padStart(2, '0'); const seconds = String(date.getUTCSeconds()).padStart(2, '0'); timestamp.textContent = `${year}-${month}-${day} ${hours}:${minutes}:${seconds} UTC`; return timestamp; } // Generate message hash for identification function generateMessageHash(element) { const textContent = element.textContent?.trim() || ''; const position = Array.from(element.parentNode?.children || []).indexOf(element); return `${textContent.substring(0, 50)}_${position}`; } // Process messages and add timestamps function processMessages() { const messages = document.querySelectorAll(CONFIG.messageSelector); messages.forEach(message => { if (message.querySelector(`.${CONFIG.timestampClass}`)) return; const hash = generateMessageHash(message); let timestamp; if (timestampData.has(hash)) { timestamp = new Date(timestampData.get(hash)); } else { timestamp = new Date(); timestampData.set(hash, timestamp.toISOString()); saveTimestampData(); } const isClaude = message.classList.contains('font-claude-response'); const isUser = message.getAttribute('data-testid') === 'user-message'; if (isClaude || isUser) { const timestampElement = createTimestamp(timestamp); const firstChild = message.firstElementChild; if (firstChild) { message.insertBefore(timestampElement, firstChild); } else { message.prepend(timestampElement); } } }); } // Intercept network requests to capture real timestamps function interceptNetworkData() { const originalFetch = window.fetch; window.fetch = function(...args) { return originalFetch.apply(this, args).then(response => { if (response.url.includes('/chat_conversations/') || response.url.includes('/messages')) { const clonedResponse = response.clone(); clonedResponse.json().then(data => { extractTimestampsFromResponse(data); }).catch(() => {}); } return response; }); }; } // Extract timestamps from API responses function extractTimestampsFromResponse(data) { if (!data) return; const messages = data.messages || data.chat_messages || []; messages.forEach(msg => { if (msg.created_at || msg.timestamp) { const timestamp = new Date(msg.created_at || msg.timestamp); const content = msg.content || msg.text || ''; const hash = `${content.substring(0, 50)}_`; for (const [key, value] of timestampData.entries()) { if (key.startsWith(hash)) { timestampData.set(key, timestamp.toISOString()); break; } } } }); saveTimestampData(); setTimeout(processMessages, 100); } // Setup mutation observer function setupObserver() { const observer = new MutationObserver((mutations) => { let shouldProcess = false; mutations.forEach((mutation) => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { if ( node.nodeType === 1 && (node.matches?.(CONFIG.messageSelector) || node.querySelector?.(CONFIG.messageSelector)) ) { shouldProcess = true; break; } } } }); if (shouldProcess) { setTimeout(processMessages, CONFIG.observerDelay); } }); observer.observe(document.body, { childList: true, subtree: true }); return observer; } // Initialize the script function init() { console.log('Claude Timestamp Revealer: Initializing...'); loadTimestampData(); injectStyles(); interceptNetworkData(); setTimeout(processMessages, 1000); setupObserver(); setInterval(processMessages, 10000); console.log('Claude Timestamp Revealer: Ready'); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { setTimeout(init, 100); } })();