Claude聊天对话导出md格式

在claude对话页面添加一个下载按钮,点击即可将对话记录下载到本地

// ==UserScript==
// @name         Claude聊天对话导出md格式
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  在claude对话页面添加一个下载按钮,点击即可将对话记录下载到本地
// @author       angury
// @match        *://claude.ai/*
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
        .export-btn {
            height: 36px;
            width: 36px;
            background: none;
            border: none;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 0.375rem;
            transition-property: color, background-color, transform;
            color: var(--text-200, #666);
        }
        .export-btn:hover {
            background-color: var(--bg-500, #f3f4f6);
            opacity: 0.4;
            color: var(--text-100, #333);
        }
        .export-btn:active {
            transform: scale(0.95);
            background-color: var(--bg-400, #e5e7eb);
        }
        .export-btn svg {
            width: 20px;
            height: 20px;
        }
    `);

    let currentObserver = null;
    let currentPath = window.location.pathname;

    // 从URL获取聊天ID
    function getChatId() {
        const match = window.location.pathname.match(/\/chat\/([^/]+)/);
        return match ? match[1] : 'unknown';
    }

    // 生成时间戳
    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 createExportButton() {
        // 检查是否已存在按钮
        const existingButton = document.querySelector('.export-btn');
        if (existingButton) {
            return null;
        }

        const button = document.createElement('button');
        button.className = 'export-btn inline-flex items-center justify-center relative shrink-0';
        button.innerHTML = `
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="-translate-y-[0.5px]">
                <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
                <polyline points="7 10 12 15 17 10"/>
                <line x1="12" y1="15" x2="12" y2="3"/>
            </svg>
        `;
        button.addEventListener('click', exportChat);
        return button;
    }

    // 尝试插入按钮
    function tryInsertButton() {
        const starButton = document.querySelector('[data-testid="conversation-star-button"]');
        if (starButton && starButton.parentElement) {
            const button = createExportButton();
            if (button) {
                starButton.parentElement.insertBefore(button, starButton);
                return true;
            }
        }
        return false;
    }

    // 清理旧的观察器
    function cleanupObserver() {
        if (currentObserver) {
            currentObserver.disconnect();
            currentObserver = null;
        }
    }

    // 使用MutationObserver监听DOM变化
    function setupObserver() {
        cleanupObserver();

        const observer = new MutationObserver((mutations, obs) => {
            if (tryInsertButton()) {
                // 保持观察器运行,因为在SPA中DOM可能会再次变化
                return;
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        currentObserver = observer;

        // 同时也立即尝试一次
        tryInsertButton();
    }

    // 获取聊天内容
    function getChatContent() {
        const messages = [];
        const chatElements = document.querySelectorAll('[data-testid="user-message"], .font-claude-message');

        chatElements.forEach((element) => {
            const isUser = element.hasAttribute('data-testid') && element.getAttribute('data-testid') === 'user-message';
            const role = isUser ? 'User' : 'Assistant';
            const content = element.textContent.trim();

            if (content) {
                messages.push({
                    role: role,
                    content: content,
                    timestamp: new Date().toISOString()
                });
            }
        });

        return messages;
    }

    // 格式化聊天内容
    function formatChatContent(messages) {
        const now = new Date();
        const timeStr = now.toLocaleString('zh-CN', {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit',
            hour12: false
        }).replace(/\//g, '-');

        let formatted = `Time: ${timeStr}\n`;
        formatted += `URL: ${window.location.href}\n\n\n`;

        messages.forEach((msg) => {
            formatted += `# ${msg.role === 'User' ? 'Ur' : 'AI'} (${msg.timestamp})\n\n\n`;
            formatted += `${msg.content}\n\n\n`;
        });

        return formatted;
    }

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

    // 导出聊天记录主函数
    function exportChat() {
        try {
            const messages = getChatContent();
            const formatted = formatChatContent(messages);
            const chatId = getChatId();
            const timestamp = generateTimestamp();
            const filename = `${timestamp}_${chatId}.md`;
            downloadFile(formatted, filename);
        } catch (error) {
            console.error('导出失败:', error);
            alert('导出失败,请检查控制台获取详细信息');
        }
    }

    // 监听 URL 变化
    function setupURLChangeListener() {
        // 使用 MutationObserver 监听 URL 变化
        const urlObserver = new MutationObserver(() => {
            const newPath = window.location.pathname;
            if (newPath !== currentPath) {
                currentPath = newPath;
                setupObserver();
            }
        });

        // 监听 body 的子元素变化
        urlObserver.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 监听 popstate 事件(处理浏览器前进/后退)
        window.addEventListener('popstate', () => {
            const newPath = window.location.pathname;
            if (newPath !== currentPath) {
                currentPath = newPath;
                setupObserver();
            }
        });
    }

    // 初始化
    function initialize() {
        setupObserver();
        setupURLChangeListener();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }
})();

QingJ © 2025

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