Claude.ai Chat Exporter (Updated)

Adds "Export Chat" button to Claude.ai (current as of Sonnet 3.7)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Claude.ai Chat Exporter (Updated)
// @description  Adds "Export Chat" button to Claude.ai (current as of Sonnet 3.7)
// @version      1.1
// @author       Merlin McKean 
// @namespace    
// @match        https://claude.ai/*
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    const API_BASE_URL = 'https://claude.ai/api';

    // Updated function to make API requests with new headers
    function apiRequest(method, endpoint, data = null) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: method,
                url: `${API_BASE_URL}${endpoint}`,
                headers: {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json',
                    // Add any required authentication headers here
                },
                data: data ? JSON.stringify(data) : null,
                onload: (response) => {
                    if (response.status >= 200 && response.status < 300) {
                        resolve(JSON.parse(response.responseText));
                    } else {
                        reject(new Error(`API request failed with status ${response.status}`));
                    }
                },
                onerror: (error) => {
                    reject(error);
                },
            });
        });
    }

    // Function to get the organization ID - updated for new API
    async function getOrganizationId() {
        try {
            const organizations = await apiRequest('GET', '/organizations');
            return organizations[0]?.uuid;
        } catch (error) {
            console.error('Error getting organization ID:', error);
            throw error;
        }
    }

    // Updated function to get conversation history
    async function getConversationHistory(orgId, chatId) {
        return await apiRequest('GET', `/organizations/${orgId}/chat_conversations/${chatId}`);
    }

    // Function to download data as a file
    function downloadData(data, filename, format) {
        let content = '';
        if (format === 'json') {
            content = JSON.stringify(data, null, 2);
        } else if (format === 'txt') {
            content = convertToTxtFormat(data);
        }

        const blob = new Blob([content], { type: 'text/plain' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();

        setTimeout(() => {
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }, 100);
    }

    // Updated function to convert conversation data to TXT format
    function convertToTxtFormat(data) {
        let txtContent = `Chat Title: ${data.name}\nDate: ${new Date().toISOString()}\n\n`;
        data.chat_messages.forEach((message) => {
            const sender = message.sender === 'human' ? 'User' : 'Claude';
            txtContent += `${sender}:\n${message.text}\n\n`;
        });
        return txtContent.trim();
    }

    // Updated function to create a modern-styled button
    function createButton(text, onClick) {
        const button = document.createElement('button');
        button.textContent = text;
        button.style.cssText = `
            position: fixed;
            bottom: 80px;
            right: 20px;
            padding: 10px 20px;
            background-color: #4a5568;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            z-index: 9999;
            font-family: system-ui, -apple-system, sans-serif;
            transition: all 0.2s;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        `;

        button.addEventListener('mouseover', () => {
            button.style.backgroundColor = '#2d3748';
        });

        button.addEventListener('mouseout', () => {
            button.style.backgroundColor = '#4a5568';
        });

        button.addEventListener('click', onClick);
        document.body.appendChild(button);
        return button;
    }

    // Updated function to initialize the export functionality
    async function initExportFunctionality() {
        const currentUrl = window.location.href;
        const chatIdMatch = currentUrl.match(/\/chat\/([a-f0-9-]+)/);

        if (chatIdMatch) {
            const chatId = chatIdMatch[1];
            try {
                const orgId = await getOrganizationId();
                const button = createButton('Export Chat', async () => {
                    const format = prompt('Enter export format (json or txt):', 'json');
                    if (format === 'json' || format === 'txt') {
                        try {
                            const chatData = await getConversationHistory(orgId, chatId);
                            const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
                            const filename = `claude-chat_${timestamp}.${format}`;
                            downloadData(chatData, filename, format);
                            alert(`Chat exported successfully in ${format.toUpperCase()} format!`);
                        } catch (error) {
                            alert('Error exporting chat. Please try again.');
                            console.error('Export error:', error);
                        }
                    } else {
                        alert('Invalid format. Please enter either "json" or "txt".');
                    }
                });
            } catch (error) {
                console.error('Error initializing export functionality:', error);
            }
        }
    }

    // Function to observe URL changes
    function observeUrlChanges() {
        let lastUrl = location.href;
        new MutationObserver(() => {
            const url = location.href;
            if (url !== lastUrl) {
                lastUrl = url;
                initExportFunctionality();
            }
        }).observe(document, { subtree: true, childList: true });
    }

    // Initialize the script when the page is ready
    function init() {
        initExportFunctionality();
        observeUrlChanges();
    }

    // Start the script when the DOM is fully loaded
    if (document.readyState === 'complete') {
        init();
    } else {
        window.addEventListener('load', init);
    }
})();