ChatGPT Deep Research Markdown Exporter

Export ChatGPT deep research content with proper markdown formatting and numbered citations

Встановити цей скрипт?
Скрипт запропонований Автором

Вам також може сподобатись Perplexity.ai Chat Exporter.

Встановити цей скрипт
// ==UserScript==
// @name         ChatGPT Deep Research Markdown Exporter
// @namespace    https://github.com/ckep1/chatgpt-research-export
// @version      1.0.0
// @description  Export ChatGPT deep research content with proper markdown formatting and numbered citations
// @author       Chris Kephart
// @match        https://chatgpt.com/*
// @match        https://chat.openai.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Function to get the base URL without fragments or query parameters
    function getBaseUrl(url) {
        try {
            const urlObj = new URL(url);
            return `${urlObj.protocol}//${urlObj.host}${urlObj.pathname}`;
        } catch (e) {
            return url;
        }
    }

    // Function to convert HTML content to markdown
    function convertToMarkdown(element) {
        let markdown = '';
        let sourceCounter = 1;
        const sourceMap = new Map(); // Track unique sources

        function processNode(node) {
            if (node.nodeType === Node.TEXT_NODE) {
                return node.textContent;
            }

            if (node.nodeType !== Node.ELEMENT_NODE) {
                return '';
            }

            const tagName = node.tagName.toLowerCase();
            let content = '';

            // Process child nodes
            for (const child of node.childNodes) {
                content += processNode(child);
            }

            switch (tagName) {
                case 'h1':
                    return `# ${content.trim()}\n\n`;
                case 'h2':
                    return `## ${content.trim()}\n\n`;
                case 'h3':
                    return `### ${content.trim()}\n\n`;
                case 'h4':
                    return `#### ${content.trim()}\n\n`;
                case 'h5':
                    return `##### ${content.trim()}\n\n`;
                case 'h6':
                    return `###### ${content.trim()}\n\n`;
                case 'p':
                    return `${content.trim()}\n\n`;
                case 'strong':
                case 'b':
                    return `**${content}**`;
                case 'em':
                case 'i':
                    return `*${content}*`;
                case 'ul':
                    return `${content}\n`;
                case 'ol':
                    return `${content}\n`;
                case 'li':
                    return `- ${content.trim()}\n`;
                case 'blockquote':
                    return `> ${content.trim()}\n\n`;
                case 'code':
                    return `\`${content}\``;
                case 'pre':
                    return `\`\`\`\n${content}\n\`\`\`\n\n`;
                case 'a': {
                    const href = node.getAttribute('href');
                    if (href) {
                        // Skip if this link is inside a citation span (already handled)
                        if (node.closest('span[data-state="closed"]')) {
                            return '';
                        }

                        const baseUrl = getBaseUrl(href);

                        // Check if we've seen this base URL before
                        if (!sourceMap.has(baseUrl)) {
                            sourceMap.set(baseUrl, sourceCounter);
                            sourceCounter++;
                        }

                        const sourceNumber = sourceMap.get(baseUrl);
                        return `([${sourceNumber}](${href}))`;
                    }
                    return content;
                }
                case 'br':
                    return '\n';
                case 'span': {
                    // Handle citation spans with data-state="closed"
                    if (node.getAttribute('data-state') === 'closed') {
                        // Find the nested link
                        const link = node.querySelector('a[href]');
                        if (link) {
                            const href = link.getAttribute('href');
                            const baseUrl = getBaseUrl(href);

                            // Check if we've seen this base URL before
                            if (!sourceMap.has(baseUrl)) {
                                sourceMap.set(baseUrl, sourceCounter);
                                sourceCounter++;
                            }

                            const sourceNumber = sourceMap.get(baseUrl);
                            return `([${sourceNumber}](${href}))`;
                        }
                        return '';
                    }
                    return content;
                }
                default:
                    return content;
            }
        }

        return processNode(element);
    }

    // Function to get today's date in YYYY-MM-DD format
    function getTodayDate() {
        const today = new Date();
        return today.toISOString().split('T')[0];
    }

    // Function to extract title from h1
    function extractTitle(researchContainer) {
        const h1 = researchContainer.querySelector('h1');
        return h1 ? h1.textContent.trim() : 'ChatGPT Research';
    }

    // Function to sanitize title for YAML frontmatter
    function sanitizeTitle(title) {
        return title
            .replace(/:/g, '-') // Replace colons with dashes
            .replace(/"/g, '\\"') // Escape double quotes
            .replace(/\\/g, '\\\\') // Escape backslashes
            .replace(/\n/g, ' ') // Replace newlines with spaces
            .replace(/\r/g, ' ') // Replace carriage returns with spaces
            .replace(/\t/g, ' ') // Replace tabs with spaces
            .replace(/\s+/g, ' ') // Collapse multiple spaces
            .trim(); // Remove leading/trailing whitespace
    }

    // Function to generate frontmatter
    function generateFrontmatter(title, url) {
        const sanitizedTitle = sanitizeTitle(title);
        return `---
title: "${sanitizedTitle}"
url: ${url}
date: ${getTodayDate()}
---

`;
    }

    // Toggle frontmatter setting
    let includeFrontmatter = GM_getValue('includeFrontmatter', false);

    function toggleFrontmatter() {
        includeFrontmatter = !includeFrontmatter;
        GM_setValue('includeFrontmatter', includeFrontmatter);
        alert(`Frontmatter ${includeFrontmatter ? 'enabled' : 'disabled'}`);
        updateMenuCommand();
    }

    function updateMenuCommand() {
        // Remove existing menu command if it exists
        if (window.menuCommandId) {
            GM_unregisterMenuCommand(window.menuCommandId);
        }

        // Register new menu command
        window.menuCommandId = GM_registerMenuCommand(
            `${includeFrontmatter ? '✓' : '✗'} Include Frontmatter`,
            toggleFrontmatter
        );
    }

    // Function to export deep research content
    function exportDeepResearch() {
        // Find the deep research result container
        const researchContainer = document.querySelector('.deep-research-result');

        if (!researchContainer) {
            alert('No deep research content found on this page.');
            return;
        }

        // Convert to markdown
        let markdown = convertToMarkdown(researchContainer);

        // Clean up extra whitespace
        markdown = markdown
            .replace(/\n\s*\n\s*\n/g, '\n\n') // Remove extra blank lines
            .replace(/^\s+|\s+$/g, '') // Trim start and end
            .replace(/\n{3,}/g, '\n\n'); // Limit to maximum 2 consecutive newlines

        // Add frontmatter if enabled
        if (includeFrontmatter) {
            const title = extractTitle(researchContainer);
            const currentUrl = window.location.href;
            const frontmatter = generateFrontmatter(title, currentUrl);
            markdown = frontmatter + markdown;
        }

        // Create and download the file
        const blob = new Blob([markdown], { type: 'text/markdown' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = 'chatgpt-research-export.md';

        // Trigger download
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);

        console.log('Deep research content exported successfully!');
    }

    // Function to copy to clipboard
    function copyDeepResearchToClipboard() {
        const researchContainer = document.querySelector('.deep-research-result');

        if (!researchContainer) {
            alert('No deep research content found on this page.');
            return;
        }

        let markdown = convertToMarkdown(researchContainer);

        // Clean up extra whitespace
        markdown = markdown
            .replace(/\n\s*\n\s*\n/g, '\n\n')
            .replace(/^\s+|\s+$/g, '')
            .replace(/\n{3,}/g, '\n\n');

        // Add frontmatter if enabled
        if (includeFrontmatter) {
            const title = extractTitle(researchContainer);
            const currentUrl = window.location.href;
            const frontmatter = generateFrontmatter(title, currentUrl);
            markdown = frontmatter + markdown;
        }

        navigator.clipboard.writeText(markdown).then(() => {
            alert('Deep research content copied to clipboard!');
        }).catch(() => {
            // Fallback for older browsers
            const textArea = document.createElement('textarea');
            textArea.value = markdown;
            document.body.appendChild(textArea);
            textArea.select();
            document.execCommand('copy');
            document.body.removeChild(textArea);
            alert('Deep research content copied to clipboard!');
        });
    }

    // Add export buttons to the page
    function addExportButtons() {
        // Check if buttons already exist
        if (document.getElementById('deep-research-export-btn')) {
            return;
        }

        // Find a good place to add the buttons (near the research container)
        const researchContainer = document.querySelector('.deep-research-result');
        if (!researchContainer) {
            return;
        }

        // Create button container
        const buttonContainer = document.createElement('div');
        buttonContainer.style.cssText = `
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 9999;
            display: flex;
            gap: 10px;
            flex-direction: column;
        `;

        // Create download button
        const downloadBtn = document.createElement('button');
        downloadBtn.id = 'deep-research-export-btn';
        downloadBtn.textContent = 'Export Research (MD)';
        downloadBtn.style.cssText = `
            background: #10a37f;
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
        `;
        downloadBtn.addEventListener('click', exportDeepResearch);

        // Create copy button
        const copyBtn = document.createElement('button');
        copyBtn.textContent = 'Copy Research (MD)';
        copyBtn.style.cssText = `
            background: #6366f1;
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
        `;
        copyBtn.addEventListener('click', copyDeepResearchToClipboard);

        buttonContainer.appendChild(downloadBtn);
        buttonContainer.appendChild(copyBtn);
        document.body.appendChild(buttonContainer);
    }

    // Watch for deep research content to appear
    function watchForResearchContent() {
        const observer = new MutationObserver((mutations) => {
            for (const mutation of mutations) {
                if (mutation.type === 'childList') {
                    const researchContainer = document.querySelector('.deep-research-result');
                    if (researchContainer && !document.getElementById('deep-research-export-btn')) {
                        addExportButtons();
                        break;
                    }
                }
            }
        });

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

    // Initialize
    setTimeout(() => {
        // Register menu command
        updateMenuCommand();

        addExportButtons();
        watchForResearchContent();
    }, 2000);

})();