ChatGPT Chat Exporter - PDF

Export your ChatGPT conversations as printable PDF files

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

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

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         ChatGPT Chat Exporter - PDF
// @namespace    https://github.com/rashidazarang/chatgpt-chat-exporter
// @version      1.0.0
// @description  Export your ChatGPT conversations as printable PDF files
// @author       Rashid Azarang
// @match        https://chat.openai.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=openai.com
// @grant        none
// @license      MIT
// @homepageURL  https://github.com/rashidazarang/chatgpt-chat-exporter
// @supportURL   https://github.com/rashidazarang/chatgpt-chat-exporter/issues
// ==/UserScript==

(function() {
    'use strict';

    // Add a button to the ChatGPT interface
    function addExportButton() {
        // Check if our button already exists
        if (document.getElementById('export-pdf-button')) return;

        // Find a suitable location to add the button
        const targetElement = document.querySelector('nav');
        if (!targetElement) return;

        // Create the button
        const button = document.createElement('button');
        button.id = 'export-pdf-button';
        button.innerHTML = 'Export as PDF';
        button.style.cssText = `
            margin: 10px;
            padding: 10px;
            border-radius: 5px;
            background-color: #10a37f;
            color: white;
            border: none;
            cursor: pointer;
            font-size: 14px;
            width: calc(100% - 20px);
        `;

        // Add click event listener
        button.addEventListener('click', exportPDF);

        // Add button to the page
        targetElement.appendChild(button);
    }

    function formatDate(date = new Date()) {
        return date.toISOString().split('T')[0];
    }

    function sanitize(text) {
        return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
    }

    function extractFormattedContent() {
        const messages = document.querySelectorAll('.text-base');
        let html = '';

        messages.forEach((msg, index) => {
            const sender = index % 2 === 0 ? 'You' : 'ChatGPT';
            const contentBlock = msg.querySelector('.whitespace-pre-wrap, .markdown, .prose');
            if (!contentBlock) return;

            const clone = contentBlock.cloneNode(true);

            clone.querySelectorAll('pre').forEach(pre => {
                const code = sanitize(pre.innerText.trim());
                pre.replaceWith(`<pre><code>${code}</code></pre>`);
            });

            clone.querySelectorAll('img, canvas').forEach(el => {
                el.replaceWith('[Image or Canvas]');
            });

            const cleanText = sanitize(clone.innerText.trim()).replace(/\n/g, '<br>');

            html += `
                <div class="message">
                    <div class="sender">${sender}</div>
                    <div class="content">${cleanText}</div>
                </div>
            `;
        });

        return html;
    }

    function exportPDF() {
        const date = formatDate();
        const source = window.location.href;
        const conversationHTML = extractFormattedContent();

        const html = `
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ChatGPT Conversation - ${date}</title>
    <style>
        body {
            font-family: 'Segoe UI', sans-serif;
            max-width: 800px;
            margin: auto;
            padding: 2rem;
            background: #fff;
            color: #333;
        }
        h1 {
            text-align: center;
        }
        .meta {
            font-size: 0.9rem;
            color: #555;
            margin-bottom: 2rem;
            text-align: center;
        }
        .message {
            margin-bottom: 2rem;
            padding-bottom: 1rem;
            border-bottom: 1px solid #ddd;
        }
        .sender {
            font-weight: bold;
            font-size: 1.1rem;
            margin-bottom: 0.5rem;
        }
        pre {
            background: #f4f4f4;
            padding: 1rem;
            overflow-x: auto;
            border-radius: 5px;
            font-family: monospace;
            font-size: 0.9rem;
        }
        code {
            white-space: pre-wrap;
        }
        .content {
            line-height: 1.5;
        }
    </style>
</head>
<body>
    <h1>ChatGPT Conversation</h1>
    <div class="meta">
        <div><strong>Date:</strong> ${date}</div>
        <div><strong>Source:</strong> <a href="${source}">${source}</a></div>
    </div>
    ${conversationHTML}
    <script>
        window.onload = () => {
            window.print();
        };
    </script>
</body>
</html>
        `;

        const blob = new Blob([html], { type: 'text/html' });
        const url = URL.createObjectURL(blob);
        window.open(url, '_blank');
    }

    // Add the export button when the DOM is fully loaded
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', addExportButton);
    } else {
        addExportButton();
    }

    // Check periodically if the button needs to be added (for SPAs like ChatGPT)
    setInterval(addExportButton, 3000);
})();