Claude聊天对话导出md格式

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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();
    }
})();