Enhanced Claude.Ai Export v2.1

Export Claude conversations with multiple formats, better structure, and robust element detection

Ekde 2025/05/25. Vidu La ĝisdata versio.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name     Enhanced Claude.Ai Export v2.1
// @description Export Claude conversations with multiple formats, better structure, and robust element detection
// @version  2.1
// @author   Original: TheAlanK & SAPIENT | Enhanced by:iikoshteruu 
// @grant    none
// @match    *://claude.ai/*
// @namespace https://github.com/iikoshteruu/Enhanced-Claude.Ai-Export-v2.0
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    console.log('Enhanced Claude Export v2.1 starting...');

    // Configuration
    const CONFIG = {
        buttonText: 'Export Full',
        formats: ['txt', 'md', 'json'],
        defaultFormat: 'md',
        debug: true,
        autoScroll: true,
        scrollDelay: 1000, // Wait time between scroll attempts
        maxScrollAttempts: 50 // Maximum scroll attempts to prevent infinite loops
    };

    let isExporting = false;

    function debugLog(message, data = null) {
        if (CONFIG.debug) {
            console.log('[Claude Export v2.1]', message, data || '');
        }
    }

    // Auto-scroll to load all conversation content
    async function loadFullConversation() {
        debugLog('Starting full conversation loading...');

        return new Promise((resolve) => {
            let scrollAttempts = 0;
            let lastScrollHeight = 0;
            let unchangedCount = 0;

            const scrollInterval = setInterval(() => {
                // Scroll to top to load older messages
                window.scrollTo(0, 0);

                // Get current scroll height
                const currentScrollHeight = document.body.scrollHeight;

                debugLog(`Scroll attempt ${scrollAttempts + 1}, Height: ${currentScrollHeight}`);

                // Check if height changed (new content loaded)
                if (currentScrollHeight === lastScrollHeight) {
                    unchangedCount++;
                } else {
                    unchangedCount = 0;
                    lastScrollHeight = currentScrollHeight;
                }

                scrollAttempts++;

                // Stop if we've tried enough times or height hasn't changed for a while
                if (scrollAttempts >= CONFIG.maxScrollAttempts || unchangedCount >= 3) {
                    clearInterval(scrollInterval);
                    debugLog(`Scroll complete. Total attempts: ${scrollAttempts}`);

                    // Scroll through entire conversation to make sure everything is loaded
                    setTimeout(() => {
                        window.scrollTo(0, 0);
                        setTimeout(() => {
                            window.scrollTo(0, document.body.scrollHeight);
                            setTimeout(() => {
                                resolve();
                            }, 1000);
                        }, 500);
                    }, 500);
                }
            }, CONFIG.scrollDelay);
        });
    }

    // Enhanced conversation detection with multiple strategies
    function getConversationData() {
        debugLog('Starting enhanced conversation data extraction...');
        const messages = [];
        let allTextContent = [];

        // Strategy 1: Try to find main conversation container
        const conversationContainer = document.querySelector('main') ||
                                    document.querySelector('[data-testid="conversation"]') ||
                                    document.querySelector('.conversation') ||
                                    document.body;

        debugLog('Using container:', conversationContainer.tagName);

        // Strategy 2: Multiple selector approaches
        const selectors = [
            // Look for message containers
            'div[class*="col-start-2"]',
            'div[class*="message"]',
            'div[data-testid*="message"]',
            '[role="presentation"] > div',
            // Look for content areas
            'div[class*="prose"]',
            'div[class*="markdown"]',
            'div[class*="content"]',
            // Look for any div with substantial text content
            'div'
        ];

        let messageElements = [];

        for (const selector of selectors) {
            try {
                const elements = Array.from(conversationContainer.querySelectorAll(selector));
                const validElements = elements.filter(el => {
                    const text = el.textContent?.trim() || '';
                    // Must have substantial text content
                    return text.length > 20 &&
                           text.length < 50000 &&
                           // Exclude navigation, buttons, etc.
                           !el.closest('nav, header, footer, button, input, select') &&
                           // Exclude elements that are likely UI components
                           !text.includes('Export') &&
                           !text.includes('New chat') &&
                           !text.includes('Settings');
                });

                debugLog(`Selector "${selector}" found ${validElements.length} valid elements`);

                if (validElements.length > 0) {
                    messageElements = validElements;
                    break;
                }
            } catch (error) {
                debugLog(`Selector "${selector}" failed:`, error.message);
            }
        }

        // Strategy 3: If no good elements found, try a different approach
        if (messageElements.length === 0) {
            debugLog('No elements found, trying fallback approach...');

            // Get all divs and filter by text content patterns
            const allDivs = Array.from(document.querySelectorAll('div'));
            messageElements = allDivs.filter(div => {
                const text = div.textContent?.trim() || '';
                return text.length > 50 &&
                       text.length < 20000 &&
                       !div.querySelector('input, button, nav, header, footer') &&
                       // Look for conversation-like patterns
                       (text.includes('Claude') ||
                        text.includes('Human') ||
                        text.includes('I ') ||
                        text.includes('You ') ||
                        text.match(/^(Hi|Hello|Can you|Could you|Please|What|How|Why)/i));
            });
        }

        debugLog(`Processing ${messageElements.length} message elements...`);

        // Process found elements
        const processedTexts = new Set(); // Avoid duplicates

        messageElements.forEach((element, index) => {
            try {
                const clone = element.cloneNode(true);

                // Remove unwanted elements more aggressively
                const unwanted = clone.querySelectorAll(
                    'svg, button, input, select, nav, header, footer, script, style, ' +
                    '[aria-hidden="true"], [class*="icon"], [class*="button"], ' +
                    '[class*="menu"], [class*="toolbar"], [class*="nav"]'
                );
                unwanted.forEach(el => el.remove());

                const text = clone.textContent?.trim() || '';

                if (text && text.length > 10 && !processedTexts.has(text)) {
                    processedTexts.add(text);

                    // Better speaker detection
                    const speaker = detectSpeaker(element, text, index, messageElements.length);

                    messages.push({
                        speaker: speaker,
                        content: text,
                        timestamp: new Date().toISOString(),
                        index: index,
                        length: text.length
                    });

                    debugLog(`Message ${index + 1}: ${speaker} (${text.length} chars)`);
                }
            } catch (error) {
                debugLog(`Error processing element ${index}:`, error.message);
            }
        });

        // Sort messages by their position in the DOM (top to bottom)
        messages.sort((a, b) => a.index - b.index);

        debugLog(`Extracted ${messages.length} unique messages`);
        return messages;
    }

    // Improved speaker detection
    function detectSpeaker(element, text, index, totalMessages) {
        // Check element classes and attributes
        const classes = element.className?.toLowerCase() || '';
        const parentClasses = element.parentElement?.className?.toLowerCase() || '';
        const testId = element.getAttribute('data-testid') || '';

        // Explicit indicators
        if (classes.includes('user') || parentClasses.includes('user') || testId.includes('user')) {
            return 'Human';
        }
        if (classes.includes('assistant') || parentClasses.includes('assistant') || testId.includes('assistant')) {
            return 'Claude';
        }

        // Content-based detection
        const humanPatterns = [
            /^(hi|hello|hey|can you|could you|please|help me|i need|i want|how do|what is|why)/i,
            /\?$/, // Questions often end with ?
            text.length < 200, // Humans often write shorter messages
            /^(ok|okay|thanks|thank you|great|perfect)/i
        ];

        const claudePatterns = [
            /^(i'll|i can|i'd be happy|here's|let me|i understand|certainly|of course|absolutely)/i,
            /```/, // Code blocks
            text.length > 500, // Claude often writes longer responses
            /^(looking at|based on|here are|to help you)/i,
            text.includes('🎯') || text.includes('📝') || text.includes('🔧') // Emojis often used by Claude
        ];

        let humanScore = 0;
        let claudeScore = 0;

        humanPatterns.forEach(pattern => {
            if (typeof pattern === 'boolean') {
                if (pattern) humanScore++;
            } else {
                if (pattern.test(text)) humanScore++;
            }
        });

        claudePatterns.forEach(pattern => {
            if (typeof pattern === 'boolean') {
                if (pattern) claudeScore++;
            } else {
                if (pattern.test(text)) claudeScore++;
            }
        });

        // If scores are tied, use alternating pattern (assuming conversation starts with human)
        if (humanScore === claudeScore) {
            return index % 2 === 0 ? 'Human' : 'Claude';
        }

        return humanScore > claudeScore ? 'Human' : 'Claude';
    }

    // Format as plain text with better structure
    function formatAsText(messages) {
        if (messages.length === 0) return 'No conversation found.';

        let output = `Claude.ai COMPLETE Conversation Export\n`;
        output += `Exported: ${new Date().toLocaleString()}\n`;
        output += `Total Messages: ${messages.length}\n`;
        output += `URL: ${window.location.href}\n`;
        output += '='.repeat(80) + '\n\n';

        messages.forEach((msg, index) => {
            output += `${msg.speaker}:\n`;
            output += `${msg.content}\n\n`;

            if (index < messages.length - 1) {
                output += '-'.repeat(50) + '\n\n';
            }
        });

        return output;
    }

    // Format as Markdown with better structure
    function formatAsMarkdown(messages) {
        if (messages.length === 0) return '# No conversation found';

        let md = `# Claude.ai Complete Conversation Export\n\n`;
        md += `**Exported:** ${new Date().toLocaleString()}  \n`;
        md += `**Total Messages:** ${messages.length}  \n`;
        md += `**URL:** ${window.location.href}  \n`;
        md += `**Export Method:** Enhanced Auto-Scroll v2.1\n\n`;
        md += `---\n\n`;

        messages.forEach(msg => {
            md += `## ${msg.speaker}\n\n`;
            md += `${msg.content}\n\n`;
        });

        return md;
    }

    // Format as JSON with full metadata
    function formatAsJSON(messages) {
        const exportData = {
            exportDate: new Date().toISOString(),
            exportTimestamp: Date.now(),
            exportVersion: '2.1',
            messageCount: messages.length,
            url: window.location.href,
            userAgent: navigator.userAgent,
            conversation: messages,
            statistics: {
                humanMessages: messages.filter(m => m.speaker === 'Human').length,
                claudeMessages: messages.filter(m => m.speaker === 'Claude').length,
                totalCharacters: messages.reduce((sum, m) => sum + m.content.length, 0),
                averageMessageLength: Math.round(messages.reduce((sum, m) => sum + m.content.length, 0) / messages.length)
            }
        };
        return JSON.stringify(exportData, null, 2);
    }

    // Download file with better error handling
    function downloadFile(content, filename, type = 'text/plain') {
        try {
            debugLog(`Downloading: ${filename} (${content.length} chars)`);

            const blob = new Blob([content], { type: type + ';charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');

            link.download = filename;
            link.href = url;
            link.style.display = 'none';

            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);

            setTimeout(() => URL.revokeObjectURL(url), 1000);

            debugLog('Download completed successfully');
            return true;
        } catch (error) {
            console.error('Download failed:', error);
            alert(`Download failed: ${error.message}`);
            return false;
        }
    }

    // Export conversation with full loading
    async function exportConversation(format) {
        if (isExporting) {
            alert('Export already in progress. Please wait...');
            return;
        }

        isExporting = true;

        try {
            debugLog(`Starting FULL export in ${format} format...`);

            // Show loading indicator
            showNotification('🔄 Loading full conversation...', 0);

            // Load full conversation first
            if (CONFIG.autoScroll) {
                await loadFullConversation();
                showNotification('📝 Extracting messages...', 3000);
            }

            // Extract messages
            const messages = getConversationData();

            if (messages.length === 0) {
                alert('No conversation content found! This might be a new chat or there could be a technical issue. Check the browser console for details.');
                return;
            }

            // Generate filename with message count
            const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
            const filename = `claude-FULL-conversation-${messages.length}msgs-${timestamp}.${format}`;

            let content, mimeType;

            switch (format) {
                case 'md':
                    content = formatAsMarkdown(messages);
                    mimeType = 'text/markdown';
                    break;
                case 'json':
                    content = formatAsJSON(messages);
                    mimeType = 'application/json';
                    break;
                default:
                    content = formatAsText(messages);
                    mimeType = 'text/plain';
            }

            const success = downloadFile(content, filename, mimeType);
            if (success) {
                hideMenu();
                showNotification(`✅ Exported ${messages.length} messages as ${format.toUpperCase()}`, 5000);
            }

        } catch (error) {
            console.error('Export failed:', error);
            alert(`Export failed: ${error.message}\n\nCheck the browser console for more details.`);
        } finally {
            isExporting = false;
        }
    }

    // Show notification with auto-hide
    function showNotification(message, duration = 3000) {
        // Remove existing notification
        const existing = document.getElementById('claude-export-notification');
        if (existing) existing.remove();

        const notification = document.createElement('div');
        notification.id = 'claude-export-notification';
        notification.textContent = message;
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: #4CAF50;
            color: white;
            padding: 12px 20px;
            border-radius: 8px;
            z-index: 10000;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
            max-width: 300px;
        `;

        document.body.appendChild(notification);

        if (duration > 0) {
            setTimeout(() => {
                if (notification.parentElement) {
                    notification.parentElement.removeChild(notification);
                }
            }, duration);
        }
    }

    // Create export menu with better styling
    function createExportMenu() {
        const menu = document.createElement('div');
        menu.id = 'claude-export-menu';
        menu.style.cssText = `
            position: fixed;
            bottom: 70px;
            right: 10px;
            background: #ffffff;
            border: 2px solid #667eea;
            border-radius: 12px;
            box-shadow: 0 8px 25px rgba(0,0,0,0.2);
            padding: 12px;
            z-index: 1000;
            display: none;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            min-width: 200px;
            color: #333333 !important;
        `;

        // Add title
        const title = document.createElement('div');
        title.textContent = 'Export Full Conversation';
        title.style.cssText = `
            font-weight: 600;
            color: #667eea !important;
            margin-bottom: 8px;
            padding-bottom: 8px;
            border-bottom: 1px solid #eee;
            font-size: 14px;
        `;
        menu.appendChild(title);

        const formats = [
            { ext: 'md', name: 'Markdown', icon: '📝', desc: 'Great for docs' },
            { ext: 'txt', name: 'Plain Text', icon: '📄', desc: 'Universal format' },
            { ext: 'json', name: 'JSON Data', icon: '📊', desc: 'Structured data' }
        ];

        formats.forEach(format => {
            const button = document.createElement('button');
            button.innerHTML = `
                <div style="display: flex; align-items: center;">
                    <span style="font-size: 16px; margin-right: 8px;">${format.icon}</span>
                    <div>
                        <div style="font-weight: 500;">${format.name}</div>
                        <div style="font-size: 11px; color: #666;">${format.desc}</div>
                    </div>
                </div>
            `;
            button.style.cssText = `
                display: block;
                width: 100%;
                padding: 10px;
                margin: 4px 0;
                border: none;
                background: transparent;
                text-align: left;
                cursor: pointer;
                border-radius: 6px;
                font-size: 14px;
                color: #333333 !important;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
                transition: background-color 0.2s;
            `;

            button.onmouseover = () => {
                button.style.background = '#f8f9ff';
                button.style.color = '#000000 !important';
            };
            button.onmouseout = () => {
                button.style.background = 'transparent';
                button.style.color = '#333333 !important';
            };

            button.onclick = () => exportConversation(format.ext);
            menu.appendChild(button);
        });

        // Debug button
        if (CONFIG.debug) {
            const debugButton = document.createElement('button');
            debugButton.innerHTML = '🔍 Debug Info';
            debugButton.style.cssText = `
                display: block;
                width: 100%;
                padding: 8px 12px;
                margin: 8px 0 4px 0;
                border: none;
                background: transparent;
                text-align: left;
                cursor: pointer;
                border-radius: 4px;
                font-size: 13px;
                border-top: 1px solid #eee;
                color: #666 !important;
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            `;

            debugButton.onclick = () => {
                const messages = getConversationData();
                console.log('Debug Info:', {
                    messagesFound: messages.length,
                    url: window.location.href,
                    sampleMessages: messages.slice(0, 3)
                });
                alert(`Found ${messages.length} messages. Check console for details.`);
                hideMenu();
            };
            menu.appendChild(debugButton);
        }

        return menu;
    }

    // Show/hide menu
    function toggleMenu() {
        const menu = document.getElementById('claude-export-menu');
        if (menu) {
            menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
        }
    }

    function hideMenu() {
        const menu = document.getElementById('claude-export-menu');
        if (menu) menu.style.display = 'none';
    }

    // Create enhanced export button
    function createExportButton() {
        const button = document.createElement('button');
        button.innerHTML = `📥 Export Full`;
        button.id = 'claude-export-button';
        button.style.cssText = `
            position: fixed;
            bottom: 10px;
            right: 10px;
            padding: 10px 16px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            border: 2px solid rgba(255,255,255,0.3);
            color: white;
            text-align: center;
            display: inline-block;
            font-size: 14px;
            font-weight: 600;
            cursor: pointer;
            border-radius: 30px;
            z-index: 999;
            box-shadow: 0 4px 15px rgba(0,0,0,0.2);
            transition: all 0.3s ease;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
        `;

        button.onmouseover = () => {
            button.style.transform = 'translateY(-3px) scale(1.05)';
            button.style.boxShadow = '0 6px 20px rgba(0,0,0,0.3)';
        };

        button.onmouseout = () => {
            button.style.transform = 'translateY(0) scale(1)';
            button.style.boxShadow = '0 4px 15px rgba(0,0,0,0.2)';
        };

        button.onclick = toggleMenu;

        return button;
    }

    // Initialize the script
    function init() {
        debugLog('Initializing Enhanced Claude Export v2.1...');

        // Remove existing elements
        const existingButton = document.getElementById('claude-export-button');
        const existingMenu = document.getElementById('claude-export-menu');
        if (existingButton) existingButton.remove();
        if (existingMenu) existingMenu.remove();

        // Create UI elements
        const button = createExportButton();
        const menu = createExportMenu();

        document.body.appendChild(button);
        document.body.appendChild(menu);

        // Close menu when clicking outside
        document.addEventListener('click', function(e) {
            if (!e.target.closest('#claude-export-menu') &&
                !e.target.closest('#claude-export-button')) {
                hideMenu();
            }
        });

        debugLog('Enhanced Claude Export v2.1 initialized successfully!');
        console.log('%c✅ Enhanced Claude Export v2.1 Ready!', 'color: green; font-weight: bold;');
        console.log('%cThis version will auto-scroll to load your FULL conversation before export.', 'color: blue;');
    }

    // Wait for page to be ready
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        setTimeout(init, 1000);
    }

    // Re-initialize on navigation
    let lastUrl = location.href;
    new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            setTimeout(init, 2000);
        }
    }).observe(document, { subtree: true, childList: true });

})();