Claude API Exporter (Minimal)

Simple export of Claude conversations and artifacts

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         Claude API Exporter (Minimal)
// @namespace    http://tampermonkey.net/
// @version      6.2.0
// @description  Simple export of Claude conversations and artifacts
// @author       MRL (Minimized)
// @match        https://claude.ai/*
// @grant        GM_registerMenuCommand
// @icon         https://www.google.com/s2/favicons?sz=64&domain=claude.ai
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // =============================================
    // UTILITIES
    // =============================================
    const sanitize = (name) => (name || 'Untitled').replace(/[\\/:*?"<>|]/g, '_').replace(/\s+/g, '_').slice(0, 100);
    const formatDate = (d) => d ? new Date(d).toLocaleString() : '';

    const timestamp = (d) => {
        const dt = new Date(d || Date.now());
        const p = (n) => String(n).padStart(2, '0');
        return `${dt.getFullYear()}-${p(dt.getMonth()+1)}-${p(dt.getDate())}_${p(dt.getHours())}-${p(dt.getMinutes())}-${p(dt.getSeconds())}`;
    };

    const download = (name, content) => {
        const url = URL.createObjectURL(new Blob([content], { type: 'text/plain' }));
        Object.assign(document.createElement('a'), { href: url, download: name }).click();
        setTimeout(() => URL.revokeObjectURL(url), 100);
    };

    const notify = (msg, type = 'info') => {
        document.querySelectorAll('.claude-notif').forEach(n => n.remove());
        const el = document.createElement('div');
        el.className = 'claude-notif';
        el.style.cssText = `position:fixed;top:20px;right:20px;padding:15px 20px;border-radius:8px;color:#fff;
            font-family:system-ui;font-size:14px;z-index:10000;box-shadow:0 4px 12px rgba(0,0,0,0.3);
            background:${type === 'error' ? '#e53935' : type === 'success' ? '#43a047' : '#1e88e5'}`;
        el.textContent = msg;
        document.body.appendChild(el);
        setTimeout(() => el.remove(), 4000);
    };

    // File extensions
    const EXT = {
        'application/vnd.ant.code': (lang) => ({
            javascript: '.js', typescript: '.ts', python: '.py', html: '.html', css: '.css',
            java: '.java', cpp: '.cpp', c: '.c', go: '.go', rust: '.rs', php: '.php',
            ruby: '.rb', json: '.json', yaml: '.yaml', sql: '.sql', bash: '.sh', markdown: '.md',
            jsx: '.jsx', tsx: '.tsx', swift: '.swift', kotlin: '.kt'
        }[(lang || '').toLowerCase()] || '.txt'),
        'text/html': '.html',
        'text/markdown': '.md',
        'image/svg+xml': '.svg',
        'application/vnd.ant.mermaid': '.mmd',
        'application/vnd.ant.react': '.jsx'
    };

    const getExt = (type, lang) => typeof EXT[type] === 'function' ? EXT[type](lang) : EXT[type] || '.txt';

    // =============================================
    // API
    // =============================================
    const getOrgId = () => {
        const c = document.cookie.split(';').find(c => c.trim().startsWith('lastActiveOrg='));
        if (!c) throw new Error('Organization ID not found');
        return c.trim().split('=')[1];
    };

    const getConvId = () => (window.location.pathname.match(/\/chat\/([^/?]+)/) || [])[1];

    const fetchConv = async (id) => {
        const cid = id || getConvId();
        if (!cid) return null;
        const r = await fetch(`/api/organizations/${getOrgId()}/chat_conversations/${cid}?tree=true&rendering_mode=messages&render_all_tools=true`);
        if (!r.ok) throw new Error(`API error: ${r.status}`);
        return r.json();
    };

    // =============================================
    // ARTIFACT EXTRACTION
    // =============================================
    const extractArtifacts = (conv) => {
        const artifacts = new Map();
        const msgs = [...conv.chat_messages].sort((a, b) => a.index - b.index);

        msgs.forEach(msg => {
            msg.content.forEach(c => {
                if (c.type === 'tool_use' && c.name === 'artifacts' && c.input) {
                    const inp = c.input;
                    if (!artifacts.has(inp.id)) {
                        artifacts.set(inp.id, { versions: [], content: '', title: inp.title || inp.id, type: inp.type, lang: inp.language });
                    }
                    const art = artifacts.get(inp.id);

                    if (inp.command === 'create') {
                        art.content = inp.content || '';
                        art.title = inp.title || art.title;
                        art.type = inp.type;
                        art.lang = inp.language;
                    } else if (inp.command === 'rewrite') {
                        art.content = inp.content || '';
                        art.title = inp.title || art.title;
                    } else if (inp.command === 'update') {
                        art.content = art.content.replace(inp.old_str || '', inp.new_str || '');
                    }

                    art.versions.push({
                        version: art.versions.length + 1,
                        command: inp.command,
                        title: inp.title || art.title,
                        content: art.content,
                        type: art.type,
                        lang: art.lang,
                        timestamp: c.stop_timestamp || msg.created_at,
                        msgUuid: msg.uuid
                    });
                }
            });
        });

        return artifacts;
    };

    // =============================================
    // MARKDOWN GENERATION
    // =============================================
    const generateMarkdown = (conv, artifacts = null, embedArtifacts = false) => {
        const lines = [];

        // Header
        if (conv.project) lines.push(`*Project:* [${conv.project.name}](https://claude.ai/project/${conv.project.uuid})  `);
        lines.push(`*URL:* https://claude.ai/chat/${conv.uuid}  `);
        lines.push(`*Created:* ${formatDate(conv.created_at)}  `);
        lines.push(`*Updated:* ${formatDate(conv.updated_at)}`);
        if (conv.model) lines.push(`*Model:* \`${conv.model}\``);
        lines.push(`\n# ${conv.name || 'Untitled'}\n`);

        const msgs = [...conv.chat_messages].sort((a, b) => a.index - b.index);

        msgs.forEach(msg => {
            const role = msg.sender === 'human' ? 'Human' : 'Claude';
            lines.push('__________\n');
            lines.push(`## ${msg.index} - ${role}  `);
            lines.push(`*Created:* ${formatDate(msg.created_at)}  `);
            lines.push(`*UUID:* \`${msg.uuid}\`\n`);

            msg.content.forEach(c => {
                if (c.type === 'text') {
                    lines.push(c.text + '\n');
                } else if (c.type === 'tool_use' && c.name === 'artifacts' && c.input) {
                    const inp = c.input;
                    lines.push(`**Artifact:** ${inp.title || inp.id}  `);
                    lines.push(`*ID:* \`${inp.id}\` | *Command:* \`${inp.command}\`\n`);

                    if (embedArtifacts && artifacts?.has(inp.id)) {
                        const art = artifacts.get(inp.id);
                        const ver = art.versions.find(v => v.msgUuid === msg.uuid);
                        if (ver) {
                            const lang = ver.type === 'text/markdown' ? 'markdown' :
                                        ver.type === 'text/html' ? 'html' :
                                        ver.type === 'application/vnd.ant.react' ? 'jsx' :
                                        (ver.lang || '').toLowerCase() || '';
                            lines.push('```' + lang);
                            lines.push(ver.content);
                            lines.push('```\n');
                        }
                    }
                } else if (c.type === 'tool_use' && c.name === 'web_search' && c.input) {
                    lines.push(`**🔍 Web Search:** \`${c.input.query}\`\n`);
                } else if (c.type === 'thinking' && c.thinking) {
                    const sum = c.summaries?.length ? c.summaries[c.summaries.length - 1]?.summary : 'Thinking...';
                    lines.push(`*[Claude thinking...]*\n<details>\n<summary>${sum}</summary>\n${c.thinking}\n</details>\n`);
                }
            });

            (msg.files_v2 || msg.files || []).forEach(f => {
                lines.push(`**File:** ${f.file_name} | *ID:* \`${f.file_uuid}\`\n`);
            });
        });

        return lines.join('\n');
    };

    // =============================================
    // EXPORT FUNCTIONS
    // =============================================
    const genFilename = (conv) => `[${timestamp(conv.created_at)}] ${sanitize(conv.name)}.md`;

    const exportConversation = async () => {
        try {
            notify('Exporting...', 'info');
            const conv = await fetchConv();
            if (!conv) { notify('No conversation found', 'error'); return; }

            download(genFilename(conv), generateMarkdown(conv));
            notify('✅ Conversation exported!', 'success');
        } catch (e) {
            console.error('[Exporter]', e);
            notify(`Export failed: ${e.message}`, 'error');
        }
    };

    const exportWithArtifacts = async (embedOnly = false) => {
        try {
            notify('Exporting...', 'info');
            const conv = await fetchConv();
            if (!conv) { notify('No conversation found', 'error'); return; }

            const artifacts = extractArtifacts(conv);
            download(genFilename(conv), generateMarkdown(conv, artifacts, true));

            if (!embedOnly) {
                let count = 0;
                artifacts.forEach((art) => {
                    if (art.versions.length) {
                        const final = art.versions[art.versions.length - 1];
                        const ext = getExt(final.type, final.lang);
                        const fn = `[${timestamp(final.timestamp)}] ${sanitize(conv.name)} - ${sanitize(final.title)}${ext}`;
                        download(fn, final.content);
                        count++;
                    }
                });
                notify(`✅ Exported + ${count} artifact files!`, 'success');
            } else {
                notify(`✅ Exported with ${artifacts.size} artifacts inline!`, 'success');
            }
        } catch (e) {
            console.error('[Exporter]', e);
            notify(`Export failed: ${e.message}`, 'error');
        }
    };

    // =============================================
    // INIT - v6.2: Removed Raw JSON option
    // =============================================
    const init = () => {
        console.log('[Claude Exporter v6.2] Ready');
        GM_registerMenuCommand('📄 Conversation Only', exportConversation);
        GM_registerMenuCommand('📝 Conversation + Code Inline', () => exportWithArtifacts(true));
        GM_registerMenuCommand('📁 Conversation + Separate Files', () => exportWithArtifacts(false));
    };

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