Claude.ai Chat Exporter (Updated)

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
    }
})();