Greasy Fork

Greasy Fork is available in English.

Claude Chat Exporter

Export Claude AI conversations to Markdown format

// ==UserScript==
// @name         Claude Chat Exporter
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Export Claude AI conversations to Markdown format
// @author       Vishal Agarwal
// @match        https://claude.ai/*
// @grant        none
// @license      MIT
// @homepage     https://github.com/user/claude-chat-exporter
// @supportURL   https://github.com/user/claude-chat-exporter/issues
// ==/UserScript==

(function() {
    'use strict';

    function extractConversation() {
        let markdown = "# Conversation with Claude\n\n";
        const messages = document.querySelectorAll('.font-claude-message, .font-user-message');
        
        messages.forEach((message) => {
            const isHuman = message.classList.contains('font-user-message');
            const role = isHuman ? 'Human' : 'Claude';
            markdown += `## ${role}:\n\n`;
            
            const content = isHuman ? message : message.querySelector('.grid-cols-1');
            if (content) {
                markdown += processContent(content);
            }
            
            markdown += "\n";
        });
        
        return markdown;
    }

    function processContent(element, depth = 0) {
        let markdown = '';
        const children = element.childNodes;
        
        for (let child of children) {
            if (child.nodeType === Node.TEXT_NODE) {
                markdown += child.textContent;
            } else if (child.nodeType === Node.ELEMENT_NODE) {
                switch (child.tagName) {
                    case 'P':
                        markdown += processInlineElements(child) + "\n\n";
                        break;
                    case 'OL':
                        markdown += processList(child, 'ol', depth) + "\n";
                        break;
                    case 'UL':
                        markdown += processList(child, 'ul', depth) + "\n";
                        break;
                    case 'PRE':
                        const codeBlock = child.querySelector('code');
                        if (codeBlock) {
                            const language = codeBlock.className.match(/language-(\w+)/)?.[1] || '';
                            markdown += "```" + language + "\n" + codeBlock.textContent.trim() + "\n```\n\n";
                        }
                        break;
                    default:
                        markdown += processInlineElements(child) + "\n\n";
                }
            }
        }
        
        return markdown;
    }

    function processList(listElement, listType, depth = 0) {
        let markdown = '';
        const items = listElement.children;
        const indent = '  '.repeat(depth);
        
        for (let i = 0; i < items.length; i++) {
            const item = items[i];
            const prefix = listType === 'ol' ? `${i + 1}. ` : '- ';
            markdown += indent + prefix + processInlineElements(item).trim() + "\n";
            
            const nestedLists = item.querySelectorAll(':scope > ol, :scope > ul');
            for (let nestedList of nestedLists) {
                markdown += processList(nestedList, nestedList.tagName.toLowerCase(), depth + 1);
            }
        }
        
        return markdown;
    }

    function processInlineElements(element) {
        let markdown = '';
        const children = element.childNodes;
        
        for (let child of children) {
            if (child.nodeType === Node.TEXT_NODE) {
                markdown += child.textContent;
            } else if (child.nodeType === Node.ELEMENT_NODE) {
                if (child.tagName === 'CODE') {
                    markdown += '`' + child.textContent + '`';
                } else if (child.tagName === 'OL' || child.tagName === 'UL') {
                    continue;
                } else {
                    markdown += processInlineElements(child);
                }
            }
        }
        
        return markdown;
    }

    function downloadMarkdown(content, filename) {
        const blob = new Blob([content], { type: 'text/markdown' });
        const a = document.createElement('a');
        a.href = URL.createObjectURL(blob);
        a.download = filename;
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        URL.revokeObjectURL(a.href);
    }

    function createExportButton() {
        const button = document.createElement('button');
        button.innerHTML = '📄 Export Chat';
        button.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 10000;
            padding: 10px 15px;
            background: #2563eb;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            transition: background-color 0.2s;
        `;
        
        button.addEventListener('mouseenter', () => {
            button.style.backgroundColor = '#1d4ed8';
        });
        
        button.addEventListener('mouseleave', () => {
            button.style.backgroundColor = '#2563eb';
        });
        
        button.addEventListener('click', () => {
            try {
                const conversationMarkdown = extractConversation();
                if (conversationMarkdown.trim() === "# Conversation with Claude\n\n") {
                    alert('No conversation found. Make sure you are on a Claude conversation page.');
                    return;
                }
                const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
                downloadMarkdown(conversationMarkdown, `claude_conversation_${timestamp}.md`);
            } catch (error) {
                console.error('Error exporting conversation:', error);
                alert('Error exporting conversation. Check the console for details.');
            }
        });
        
        document.body.appendChild(button);
    }

    function waitForPageLoad() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', createExportButton);
        } else {
            setTimeout(createExportButton, 1000);
        }
    }

    waitForPageLoad();
})();