Chat GPT Code Export Button

Adds Export button to code blocks in ChatGPT responses, prompts user to save code as file with predefined filename based on coding language detected from the code block's class name.

2024-07-04 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Chat GPT Code Export Button
// @namespace    http://tampermonkey.net/
// @version      2024/07/03
// @license      MIT
// @description  Adds Export button to code blocks in ChatGPT responses, prompts user to save code as file with predefined filename based on coding language detected from the code block's class name.
// @author       Muffin & Arcadie
// @match        https://chatgpt.com/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Function to add "Export" button to existing code headers
    function addExportButtonToHeaders() {
        const codeHeaders = document.querySelectorAll('.flex.items-center.relative.text-token-text-secondary.bg-token-main-surface-secondary.px-4.py-2.text-xs.font-sans.justify-between.rounded-t-md');

        codeHeaders.forEach(header => {
            // Check if "Export" button is already added
            if (header.querySelector('.export-button')) {
                return; // Skip if already added
            }

            // Find the language label
            const languageLabel = header.querySelector('span');

            // Create the "Export" button
            const exportButton = document.createElement('button');
            exportButton.innerText = 'Export';
            exportButton.classList.add('export-button'); // Add a class for styling
            exportButton.style.padding = '8px 16px';
            exportButton.style.marginRight = '8px'; // Adjust spacing if necessary
            exportButton.style.border = 'none';
            exportButton.style.borderRadius = '4px';
            exportButton.style.backgroundColor = '#e3e3e3';
            exportButton.style.color = '#333';
            exportButton.style.cursor = 'pointer';
            exportButton.style.transition = 'background-color 0.3s';

            // Insert "Export" button after the language label
            languageLabel.parentNode.insertBefore(exportButton, languageLabel.nextSibling);

            // Add click event listener for the "Export" button
            exportButton.addEventListener('click', () => {
                const codeBlock = header.parentElement.querySelector('pre code'); // Assuming structure, adjust as needed
                const language = languageLabel.textContent.trim().toLowerCase(); // Extract language
                exportCode(codeBlock, language); // Call export function
            });
        });
    }

    // Function to open File Explorer for saving the code as file
    async function exportCode(codeBlock, language) {
        let fileName;
        let fileExtension;
        let mimeType;

        // Determine filename, extension, and MIME type based on language
        switch (language) {
            case 'javascript':
            case 'js':
                fileName = 'script';
                fileExtension = '.js';
                mimeType = 'application/javascript';
                break;
            case 'html':
                fileName = 'index';
                fileExtension = '.html';
                mimeType = 'text/html';
                break;
            case 'css':
                fileName = 'styles';
                fileExtension = '.css';
                mimeType = 'text/css';
                break;
            case 'python':
            case 'py':
                fileName = 'main';
                fileExtension = '.py';
                mimeType = 'text/x-python';
                break;
            default:
                // If language cannot be determined from <span>, fallback to provided language
                switch (language.toLowerCase()) {
                    case 'javascript':
                    case 'js':
                        fileName = 'script';
                        fileExtension = '.js';
                        mimeType = 'application/javascript';
                        break;
                    case 'html':
                        fileName = 'index';
                        fileExtension = '.html';
                        mimeType = 'text/html';
                        break;
                    case 'css':
                        fileName = 'styles';
                        fileExtension = '.css';
                        mimeType = 'text/css';
                        break;
                    case 'python':
                    case 'py':
                        fileName = 'main';
                        fileExtension = '.py';
                        mimeType = 'text/x-python';
                        break;
                    case 'java':
                        fileName = 'Main';
                        fileExtension = '.java';
                        mimeType = 'text/x-java-source';
                        break;
                    case 'kotlin':
                        fileName = 'Main';
                        fileExtension = '.kt';
                        mimeType = 'text/x-kotlin';
                        break;
                    case 'c++':
                    case 'cpp':
                        fileName = 'main';
                        fileExtension = '.cpp';
                        mimeType = 'text/x-c++src';
                        break;
                    case 'c#':
                    case 'csharp':
                        fileName = 'Program';
                        fileExtension = '.cs';
                        mimeType = 'text/x-csharp';
                        break;
                    case 'c':
                        fileName = 'main';
                        fileExtension = '.c';
                        mimeType = 'text/x-csrc';
                        break;
                    case 'ruby':
                        fileName = 'script';
                        fileExtension = '.rb';
                        mimeType = 'text/x-ruby';
                        break;
                    case 'rust':
                        fileName = 'main';
                        fileExtension = '.rs';
                        mimeType = 'text/x-rustsrc';
                        break;
                    case 'php':
                        fileName = 'script';
                        fileExtension = '.php';
                        mimeType = 'text/x-php';
                        break;
                    case 'swift':
                        fileName = 'main';
                        fileExtension = '.swift';
                        mimeType = 'text/x-swift';
                        break;
                    case 'typescript':
                    case 'ts':
                        fileName = 'script';
                        fileExtension = '.ts';
                        mimeType = 'application/typescript';
                        break;
                    case 'go':
                        fileName = 'main';
                        fileExtension = '.go';
                        mimeType = 'text/x-go';
                        break;
                    case 'perl':
                        fileName = 'script';
                        fileExtension = '.pl';
                        mimeType = 'text/x-perl';
                        break;
                    case 'lua':
                        fileName = 'script';
                        fileExtension = '.lua';
                        mimeType = 'text/x-lua';
                        break;
                    default:
                        fileName = 'code';
                        fileExtension = '.txt';
                        mimeType = 'text/plain';
                        break;
                }
                break;
        }

        // Create a Blob object with the code content
        const blob = new Blob([codeBlock.innerText], { type: mimeType });

        try {
            if (window.showSaveFilePicker) {
                // Use File System Access API if available
                const fileHandle = await window.showSaveFilePicker({
                    suggestedName: fileName + fileExtension,
                    types: [
                        {
                            description: language,
                            accept: {
                                [mimeType]: [fileExtension],
                            },
                        },
                    ],
                });

                const writable = await fileHandle.createWritable();
                await writable.write(blob);
                await writable.close();
            } else {
                // Fallback for browsers that do not support showSaveFilePicker
                const downloadLink = document.createElement('a');
                downloadLink.href = URL.createObjectURL(blob);
                downloadLink.download = fileName + fileExtension;
                downloadLink.style.display = 'none';
                document.body.appendChild(downloadLink);
                downloadLink.click();
                URL.revokeObjectURL(downloadLink.href);
                document.body.removeChild(downloadLink);
            }
        } catch (error) {
            console.error('Save file dialog was canceled or failed', error);
        }
    }

    // Observe the document for changes and add "Export" button to new code headers
    const observer = new MutationObserver(addExportButtonToHeaders);
    observer.observe(document.body, { childList: true, subtree: true });

    // Initial processing of existing code headers
    addExportButtonToHeaders();

    // Add custom CSS styles if needed
})();