您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds functionality to ChatGPT code blocks, including options to save or copy code snippets.
当前为
// ==UserScript== // @name ChatGPT Code Tools // @name:el Εργαλεία Κώδικα για το ChatGPT // @namespace https://github.com/CarpeNoctemXD/ // @version 1.1.11 // @description Adds functionality to ChatGPT code blocks, including options to save or copy code snippets. // @description:el Προσθέτει λειτουργικότητα στα μπλοκ κώδικα του ChatGPT, συμπεριλαμβανομένων επιλογών για αποθήκευση ή αντιγραφή αποσπασμάτων κώδικα. // @author CarpeNoctemXD // @match *://chatgpt.com/* // @match *://chat.openai.com/* // @icon https://chatgpt.com/favicon.ico // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; const SCRIPT_VERSION = '1.1.11'; // Function to check for updates const updateCheck = () => { fetch('https://raw.githubusercontent.com/CarpeNoctemXD/UserScripts/main/chatgpt/chatgpt_code_tools.js?t=' + Date.now(), { method: 'GET', headers: { 'Cache-Control': 'no-cache' } }) .then(response => response.text()) .then(data => { const latestVerMatch = /@version\s+(.*)/.exec(data); if (latestVerMatch) { const latestVer = latestVerMatch[1]; const currentSubVers = SCRIPT_VERSION.split('.').map(Number); const latestSubVers = latestVer.split('.').map(Number); let isOutdated = false; for (let i = 0; i < Math.max(currentSubVers.length, latestSubVers.length); i++) { if ((latestSubVers[i] || 0) > (currentSubVers[i] || 0)) { isOutdated = true; break; } else if ((latestSubVers[i] || 0) < (currentSubVers[i] || 0)) { break; } } if (isOutdated) { alert(`Update available! 🚀\nA new version (v${latestVer}) is available! Click OK to download the update.`); window.open('https://raw.githubusercontent.com/CarpeNoctemXD/UserScripts/main/chatgpt/chatgpt_code_tools.js?t=' + Date.now(), '_blank'); } else { console.log('Your script is up-to-date.'); } } }) .catch(err => console.error('Failed to check for updates:', err)); }; // Function to get MIME type based on file extension const getMimeType = (filename) => { const ext = filename.split('.').pop(); switch (ext) { case 'py': return 'text/x-python'; case 'js': return 'application/javascript'; case 'html': return 'text/html'; case 'css': return 'text/css'; case 'java': return 'text/x-java-source'; case 'cs': return 'text/x-csharp'; case 'cpp': return 'text/x-c++src'; case 'json': return 'application/json'; case 'rb': return 'text/x-ruby'; case 'pl': return 'text/x-perl'; case 'swift': return 'text/x-swift'; case 'kt': return 'text/x-kotlin'; case 'go': return 'text/x-go'; case 'ts': return 'application/typescript'; case 'dart': return 'application/dart'; case 'sql': return 'application/sql'; case 'sh': return 'application/x-shellscript'; case 'ps1': return 'application/powershell'; case 'xml': return 'application/xml'; case 'yaml': return 'application/x-yaml'; case 'toml': return 'application/toml'; case 'ini': return 'text/plain'; case 'csv': return 'text/csv'; case 'md': return 'text/markdown'; case 'xlsx': return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; case 'bat': return 'application/x-batch'; default: return 'text/plain'; } }; // Function to determine file extension based on the code block's language const getFileExtension = (languageClass) => { if (!languageClass) return 'txt'; // Default to .txt if no class is found if (languageClass.includes('language-python')) return 'py'; if (languageClass.includes('language-javascript') || languageClass.includes('language-js')) return 'js'; if (languageClass.includes('language-html')) return 'html'; if (languageClass.includes('language-css')) return 'css'; if (languageClass.includes('language-java')) return 'java'; if (languageClass.includes('language-csharp')) return 'cs'; if (languageClass.includes('language-cpp')) return 'cpp'; if (languageClass.includes('language-json')) return 'json'; if (languageClass.includes('language-ruby')) return 'rb'; if (languageClass.includes('language-perl')) return 'pl'; if (languageClass.includes('language-swift')) return 'swift'; if (languageClass.includes('language-kotlin')) return 'kt'; if (languageClass.includes('language-go')) return 'go'; if (languageClass.includes('language-typescript')) return 'ts'; if (languageClass.includes('language-dart')) return 'dart'; if (languageClass.includes('language-sql')) return 'sql'; if (languageClass.includes('language-shell') || languageClass.includes('language-bash') || languageClass.includes('language-sh')) return 'sh'; if (languageClass.includes('language-powershell') || languageClass.includes('language-ps1')) return 'ps1'; if (languageClass.includes('language-xml')) return 'xml'; if (languageClass.includes('language-yaml')) return 'yaml'; if (languageClass.includes('language-toml')) return 'toml'; if (languageClass.includes('language-ini')) return 'ini'; if (languageClass.includes('language-csv')) return 'csv'; if (languageClass.includes('language-markdown') || languageClass.includes('language-md')) return 'md'; if (languageClass.includes('language-xlsx')) return 'xlsx'; if (languageClass.includes('language-bat') || languageClass.includes('language-batch')) return 'bat'; return 'txt'; // Default to .txt if no matching language is found }; // Function to download the text as a file const downloadFile = (text, filename, button) => { const blob = new Blob([text], { type: getMimeType(filename) }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); setButtonState(button, 'working', 'download'); }; // Function to copy text to the clipboard const copyToClipboard = (text, button) => { navigator.clipboard.writeText(text).then(() => { setButtonState(button, 'working', 'copy'); }).catch((err) => { console.error('Failed to copy code: ', err); setButtonState(button, 'error', 'copy'); }); }; // Function to set the button state and color const setButtonState = (button, state, type) => { switch (state) { case 'working': button.textContent = type === 'download' ? 'Saving...' : 'Copied!'; button.style.backgroundColor = 'green'; break; case 'error': button.textContent = type === 'download' ? 'Could not download' : 'Could not copy'; button.style.backgroundColor = 'red'; break; case 'standby': default: button.textContent = button.dataset.defaultText || 'Unknown'; button.style.backgroundColor = '#007bff'; // Blue break; } button.style.color = 'white'; // Revert the button to standby after 5 seconds if (state === 'working' || state === 'error') { setTimeout(() => { setButtonState(button, 'standby', type); }, 5000); // 5 seconds } }; // Function to add save and copy buttons to a code block const addButtonsToCodeBlock = (codeBlock) => { // Ensure existing buttons are not duplicated if (codeBlock.querySelector('.code-buttons-wrapper')) return; const wrapper = document.createElement('div'); wrapper.classList.add('code-buttons-wrapper'); wrapper.style.position = 'relative'; wrapper.style.display = 'flex'; wrapper.style.justifyContent = 'flex-start'; wrapper.style.gap = '8px'; wrapper.style.marginTop = '8px'; const saveButton = document.createElement('button'); saveButton.textContent = 'Save Code'; styleButton(saveButton); saveButton.addEventListener('click', (e) => { e.stopPropagation(); const codeElement = codeBlock.querySelector('code'); if (codeElement) { const text = codeElement.textContent; const languageClass = codeElement.className; const extension = getFileExtension(languageClass); downloadFile(text, `code.${extension}`, saveButton); } }); const copyButton = document.createElement('button'); copyButton.textContent = 'Copy Code'; styleButton(copyButton); copyButton.addEventListener('click', (e) => { e.stopPropagation(); const codeElement = codeBlock.querySelector('code'); if (codeElement) { const text = codeElement.textContent; copyToClipboard(text, copyButton); } }); // Set default text for buttons saveButton.dataset.defaultText = 'Save Code'; copyButton.dataset.defaultText = 'Copy Code'; codeBlock.parentNode.insertBefore(wrapper, codeBlock.nextSibling); wrapper.appendChild(saveButton); wrapper.appendChild(copyButton); }; // Function to style the buttons const styleButton = (button) => { Object.assign(button.style, { display: 'inline-block', padding: '8px', background: '#007bff', // Default standby color color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer', transition: 'background-color 0.3s ease', }); button.addEventListener('mouseover', () => { if (button.textContent === 'Saving...' || button.textContent === 'Copied!' || button.textContent === 'Could not download' || button.textContent === 'Could not copy') { button.style.backgroundColor = button.textContent.includes('Could not') ? 'darkred' : 'darkgreen'; } else { button.style.backgroundColor = '#0056b3'; // Darker blue for standby } }); button.addEventListener('mouseout', () => { if (button.textContent === 'Saving...' || button.textContent === 'Copied!' || button.textContent === 'Could not download' || button.textContent === 'Could not copy') { button.style.backgroundColor = button.textContent.includes('Could not') ? 'red' : 'green'; } else { button.style.backgroundColor = '#007bff'; // Default blue for standby } }); }; // Function to observe code blocks and add buttons const observeCodeBlocks = () => { const codeBlocks = document.querySelectorAll('pre:not(.processed)'); codeBlocks.forEach(block => { addButtonsToCodeBlock(block); block.classList.add('processed'); }); }; // Mutation observer to detect new code blocks added dynamically const observer = new MutationObserver(observeCodeBlocks); observer.observe(document.body, { childList: true, subtree: true }); // Initial call to process existing code blocks observeCodeBlocks(); })();