您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Export Claude conversations using API
当前为
// ==UserScript== // @name Claude Exporter 0.7 // @namespace http://tampermonkey.net/ // @version 0.7 // @description Export Claude conversations using API // @author MRL // @match https://claude.ai/chat/* // @grant GM_registerMenuCommand // @grant GM_download // @license MIT // ==/UserScript== (function() { 'use strict'; // Utility functions function generateTimestamp() { const now = new Date(); return now.getFullYear() + String(now.getMonth() + 1).padStart(2, '0') + String(now.getDate()).padStart(2, '0') + String(now.getHours()).padStart(2, '0') + String(now.getMinutes()).padStart(2, '0') + String(now.getSeconds()).padStart(2, '0'); } function sanitizeFileName(name) { return name.replace(/[\\/:*?"<>|]/g, '_') .replace(/\s+/g, '_') .replace(/__+/g, '_') .replace(/^_+|_+$/g, '') .slice(0, 100); } function downloadFile(filename, content) { const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; link.click(); setTimeout(() => { URL.revokeObjectURL(url); }, 100); } function showNotification(message, type = "info") { const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 15px 20px; border-radius: 5px; color: white; font-family: system-ui, -apple-system, sans-serif; font-size: 14px; z-index: 10000; max-width: 400px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); `; if (type === "error") { notification.style.backgroundColor = '#f44336'; } else if (type === "success") { notification.style.backgroundColor = '#4CAF50'; } else { notification.style.backgroundColor = '#2196F3'; } notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 5000); } // API functions function getConversationId() { const match = window.location.pathname.match(/\/chat\/([^/?]+)/); return match ? match[1] : null; } function getOrgId() { const cookies = document.cookie.split(';'); for (const cookie of cookies) { const [name, value] = cookie.trim().split('='); if (name === 'lastActiveOrg') { return value; } } throw new Error('Could not find organization ID'); } async function getConversationData() { const conversationId = getConversationId(); if (!conversationId) { throw new Error('Not in a conversation'); } const orgId = getOrgId(); const response = await fetch(`/api/organizations/${orgId}/chat_conversations/${conversationId}?tree=true&rendering_mode=messages&render_all_tools=true`); if (!response.ok) { throw new Error(`API request failed: ${response.status}`); } return await response.json(); } // Text processing functions async function getTextFromContent(content) { let textPieces = []; if (content.text) { textPieces.push(content.text); } if (content.input) { textPieces.push(JSON.stringify(content.input)); } if (content.content) { if (Array.isArray(content.content)) { for (const nestedContent of content.content) { textPieces = textPieces.concat(await getTextFromContent(nestedContent)); } } else if (typeof content.content === 'object') { textPieces = textPieces.concat(await getTextFromContent(content.content)); } } return textPieces; } // Artifact processing functions function extractArtifacts(conversationData) { const artifacts = new Map(); // Map<artifactId, Array<{version, command, uuid, content, title, old_str, new_str}>> conversationData.chat_messages.forEach(message => { message.content.forEach(content => { if (content.type === 'tool_use' && content.name === 'artifacts' && content.input) { const input = content.input; const artifactId = input.id; if (!artifacts.has(artifactId)) { artifacts.set(artifactId, []); } const versions = artifacts.get(artifactId); versions.push({ version: versions.length + 1, command: input.command, uuid: input.version_uuid, content: input.content || '', old_str: input.old_str || '', new_str: input.new_str || '', title: input.title || `Artifact ${artifactId}`, timestamp: message.created_at }); } }); }); return artifacts; } // Artifact processing functions for command update function applyUpdate(previousContent, oldStr, newStr) { if (!previousContent || !oldStr) { console.warn('Cannot apply update: missing previousContent or oldStr'); return previousContent || ''; } // Apply the string replacement const updatedContent = previousContent.replace(oldStr, newStr); if (updatedContent === previousContent) { console.warn('Update did not change content - old string not found'); console.warn('Looking for:', oldStr.substring(0, 100) + '...'); console.warn('In content length:', previousContent.length); // Попробуем найти похожие строки для отладки const lines = previousContent.split('\n'); const oldLines = oldStr.split('\n'); if (oldLines.length > 0) { const firstOldLine = oldLines[0].trim(); const foundLine = lines.find(line => line.includes(firstOldLine)); if (foundLine) { console.warn('Found similar line:', foundLine); } } } return updatedContent; } function buildArtifactVersions(artifacts) { const processedArtifacts = new Map(); artifacts.forEach((versions, artifactId) => { const processedVersions = []; let currentContent = ''; versions.forEach((version, index) => { let changeDescription = ''; switch (version.command) { case 'create': currentContent = version.content; changeDescription = 'Created'; break; case 'rewrite': currentContent = version.content; changeDescription = 'Rewritten'; break; case 'update': const oldContent = currentContent; currentContent = applyUpdate(currentContent, version.old_str, version.new_str); // Создаем более информативное описание изменений const oldPreview = version.old_str ? version.old_str.substring(0, 100) + '...' : ''; const newPreview = version.new_str ? version.new_str.substring(0, 100) + '...' : ''; changeDescription = `Updated: "${oldPreview}" → "${newPreview}"`; // Добавляем информацию о том, сколько символов изменилось const oldLength = oldContent.length; const newLength = currentContent.length; const lengthDiff = newLength - oldLength; if (lengthDiff > 0) { changeDescription += ` (+${lengthDiff} chars)`; } else if (lengthDiff < 0) { changeDescription += ` (${lengthDiff} chars)`; } break; default: console.warn(`Unknown command: ${version.command}`); break; } processedVersions.push({ ...version, fullContent: currentContent, changeDescription: changeDescription }); }); processedArtifacts.set(artifactId, processedVersions); }); return processedArtifacts; } // Export functions function generateConversationMarkdown(conversationData) { let markdown = ''; // Header markdown += `# ${conversationData.name}\n\n`; markdown += `*URL: https://claude.ai/chat/${conversationData.uuid} *\n`; // Project info (if available) if (conversationData.project) { markdown += `*Project:* [${conversationData.project.name}] (https://claude.ai/project/${conversationData.project.uuid})\n`; } markdown += `*Сreated: ${conversationData.created_at}*\n`; markdown += `*Updated: ${conversationData.updated_at}*\n`; markdown += `*Exported on: ${new Date().toLocaleString()}*\n`; if (conversationData.model) { markdown += `*Model: ${conversationData.model}*\n`; } markdown += `\n`; // Messages conversationData.chat_messages.forEach(message => { const role = message.sender === 'human' ? 'Human' : 'Claude'; markdown += `## ${role}\n`; markdown += `*UUID:* \`${message.uuid}\`\n`; markdown += `*Created:* ${message.created_at}\n\n`; message.content.forEach(content => { if (content.type === 'text') { markdown += content.text + '\n\n'; } else if (content.type === 'tool_use' && content.name === 'artifacts') { const input = content.input; markdown += `**Artifact Created:** ${input.title}\n`; markdown += `*ID:* \`${input.id}\`\n`; markdown += `*Command:* \`${input.command}\`\n\n`; } else if (content.type === 'thinking') { if (content.thinking) { markdown += `*[Claude thinking...]*\n\n`; markdown += `<details>\n<summary>Thinking process</summary>\n\n`; markdown += content.thinking + '\n\n'; markdown += `</details>\n\n`; } else { markdown += `*[Claude thinking...]*\n\n`; } } }); }); return markdown; } async function exportConversation(finalVersionsOnly = false) { try { showNotification('Fetching conversation data...', 'info'); const conversationData = await getConversationData(); const timestamp = generateTimestamp(); const conversationId = conversationData.uuid; const safeTitle = sanitizeFileName(conversationData.name); // Export main conversation const conversationMarkdown = generateConversationMarkdown(conversationData); const conversationFilename = `${timestamp}_${conversationId}__${safeTitle}.md`; downloadFile(conversationFilename, conversationMarkdown); // Extract and process artifacts const rawArtifacts = extractArtifacts(conversationData); const processedArtifacts = buildArtifactVersions(rawArtifacts); if (processedArtifacts.size === 0) { showNotification('No artifacts found in conversation', 'info'); return; } let totalExported = 0; // Export artifacts processedArtifacts.forEach((versions, artifactId) => { const versionsToExport = finalVersionsOnly ? [versions[versions.length - 1]] : // Only last version versions; // All versions versionsToExport.forEach(version => { const safeArtifactTitle = sanitizeFileName(version.title); const filename = `${timestamp}_${conversationId}_${artifactId}_v${version.version}_${safeArtifactTitle}.md`; let content = `# ${version.title}\n\n`; content += `*Artifact ID:* \`${artifactId}\`\n`; content += `*Version:* ${version.version}\n`; content += `*Command:* \`${version.command}\`\n`; content += `*UUID:* \`${version.uuid}\`\n`; content += `*Created:* ${version.timestamp}\n`; // Добавляем информацию об изменениях if (version.changeDescription) { content += `*Change:* ${version.changeDescription}\n`; } content += '\n---\n\n'; content += version.fullContent; downloadFile(filename, content); totalExported++; }); }); const mode = finalVersionsOnly ? 'final versions' : 'all versions'; showNotification(`Export completed! Downloaded conversation + ${totalExported} artifacts (${mode})`, 'success'); } catch (error) { console.error('Export failed:', error); showNotification(`Export failed: ${error.message}`, 'error'); } } // Initialize function init() { console.log('[Claude API Exporter] Initializing...'); // Register menu commands GM_registerMenuCommand('Export Conversation + Final Artifact Versions', () => exportConversation(true)); GM_registerMenuCommand('Export Conversation + All Artifact Versions', () => exportConversation(false)); } // Start when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();