ChatGPT Code Tools

Adds functionality to ChatGPT code blocks, including options to save or copy code snippets.

当前为 2025-05-29 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name ChatGPT Code Tools
  3. // @name:el Εργαλεία Κώδικα για το ChatGPT
  4. // @namespace https://github.com/CarpeNoctemXD/
  5. // @version 1.1.11
  6. // @description Adds functionality to ChatGPT code blocks, including options to save or copy code snippets.
  7. // @description:el Προσθέτει λειτουργικότητα στα μπλοκ κώδικα του ChatGPT, συμπεριλαμβανομένων επιλογών για αποθήκευση ή αντιγραφή αποσπασμάτων κώδικα.
  8. // @author CarpeNoctemXD
  9. // @match *://chatgpt.com/*
  10. // @match *://chat.openai.com/*
  11. // @icon https://chatgpt.com/favicon.ico
  12. // @grant none
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. const SCRIPT_VERSION = '1.1.11';
  20.  
  21. // Function to check for updates
  22. const updateCheck = () => {
  23. fetch('https://raw.githubusercontent.com/CarpeNoctemXD/UserScripts/main/chatgpt/chatgpt_code_tools.js?t=' + Date.now(), {
  24. method: 'GET',
  25. headers: { 'Cache-Control': 'no-cache' }
  26. })
  27. .then(response => response.text())
  28. .then(data => {
  29. const latestVerMatch = /@version\s+(.*)/.exec(data);
  30. if (latestVerMatch) {
  31. const latestVer = latestVerMatch[1];
  32. const currentSubVers = SCRIPT_VERSION.split('.').map(Number);
  33. const latestSubVers = latestVer.split('.').map(Number);
  34.  
  35. let isOutdated = false;
  36. for (let i = 0; i < Math.max(currentSubVers.length, latestSubVers.length); i++) {
  37. if ((latestSubVers[i] || 0) > (currentSubVers[i] || 0)) {
  38. isOutdated = true;
  39. break;
  40. } else if ((latestSubVers[i] || 0) < (currentSubVers[i] || 0)) {
  41. break;
  42. }
  43. }
  44.  
  45. if (isOutdated) {
  46. alert(`Update available! 🚀\nA new version (v${latestVer}) is available! Click OK to download the update.`);
  47. window.open('https://raw.githubusercontent.com/CarpeNoctemXD/UserScripts/main/chatgpt/chatgpt_code_tools.js?t=' + Date.now(), '_blank');
  48. } else {
  49. console.log('Your script is up-to-date.');
  50. }
  51. }
  52. })
  53. .catch(err => console.error('Failed to check for updates:', err));
  54. };
  55.  
  56. // Function to get MIME type based on file extension
  57. const getMimeType = (filename) => {
  58. const ext = filename.split('.').pop();
  59. switch (ext) {
  60. case 'py': return 'text/x-python';
  61. case 'js': return 'application/javascript';
  62. case 'html': return 'text/html';
  63. case 'css': return 'text/css';
  64. case 'java': return 'text/x-java-source';
  65. case 'cs': return 'text/x-csharp';
  66. case 'cpp': return 'text/x-c++src';
  67. case 'json': return 'application/json';
  68. case 'rb': return 'text/x-ruby';
  69. case 'pl': return 'text/x-perl';
  70. case 'swift': return 'text/x-swift';
  71. case 'kt': return 'text/x-kotlin';
  72. case 'go': return 'text/x-go';
  73. case 'ts': return 'application/typescript';
  74. case 'dart': return 'application/dart';
  75. case 'sql': return 'application/sql';
  76. case 'sh': return 'application/x-shellscript';
  77. case 'ps1': return 'application/powershell';
  78. case 'xml': return 'application/xml';
  79. case 'yaml': return 'application/x-yaml';
  80. case 'toml': return 'application/toml';
  81. case 'ini': return 'text/plain';
  82. case 'csv': return 'text/csv';
  83. case 'md': return 'text/markdown';
  84. case 'xlsx': return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
  85. case 'bat': return 'application/x-batch';
  86. default: return 'text/plain';
  87. }
  88. };
  89.  
  90. // Function to determine file extension based on the code block's language
  91. const getFileExtension = (languageClass) => {
  92. if (!languageClass) return 'txt'; // Default to .txt if no class is found
  93.  
  94. if (languageClass.includes('language-python')) return 'py';
  95. if (languageClass.includes('language-javascript') || languageClass.includes('language-js')) return 'js';
  96. if (languageClass.includes('language-html')) return 'html';
  97. if (languageClass.includes('language-css')) return 'css';
  98. if (languageClass.includes('language-java')) return 'java';
  99. if (languageClass.includes('language-csharp')) return 'cs';
  100. if (languageClass.includes('language-cpp')) return 'cpp';
  101. if (languageClass.includes('language-json')) return 'json';
  102. if (languageClass.includes('language-ruby')) return 'rb';
  103. if (languageClass.includes('language-perl')) return 'pl';
  104. if (languageClass.includes('language-swift')) return 'swift';
  105. if (languageClass.includes('language-kotlin')) return 'kt';
  106. if (languageClass.includes('language-go')) return 'go';
  107. if (languageClass.includes('language-typescript')) return 'ts';
  108. if (languageClass.includes('language-dart')) return 'dart';
  109. if (languageClass.includes('language-sql')) return 'sql';
  110. if (languageClass.includes('language-shell') || languageClass.includes('language-bash') || languageClass.includes('language-sh')) return 'sh';
  111. if (languageClass.includes('language-powershell') || languageClass.includes('language-ps1')) return 'ps1';
  112. if (languageClass.includes('language-xml')) return 'xml';
  113. if (languageClass.includes('language-yaml')) return 'yaml';
  114. if (languageClass.includes('language-toml')) return 'toml';
  115. if (languageClass.includes('language-ini')) return 'ini';
  116. if (languageClass.includes('language-csv')) return 'csv';
  117. if (languageClass.includes('language-markdown') || languageClass.includes('language-md')) return 'md';
  118. if (languageClass.includes('language-xlsx')) return 'xlsx';
  119. if (languageClass.includes('language-bat') || languageClass.includes('language-batch')) return 'bat';
  120.  
  121. return 'txt'; // Default to .txt if no matching language is found
  122. };
  123.  
  124. // Function to download the text as a file
  125. const downloadFile = (text, filename, button) => {
  126. const blob = new Blob([text], { type: getMimeType(filename) });
  127. const url = URL.createObjectURL(blob);
  128. const a = document.createElement('a');
  129. a.href = url;
  130. a.download = filename;
  131. document.body.appendChild(a);
  132. a.click();
  133. document.body.removeChild(a);
  134. URL.revokeObjectURL(url);
  135. setButtonState(button, 'working', 'download');
  136. };
  137.  
  138. // Function to copy text to the clipboard
  139. const copyToClipboard = (text, button) => {
  140. navigator.clipboard.writeText(text).then(() => {
  141. setButtonState(button, 'working', 'copy');
  142. }).catch((err) => {
  143. console.error('Failed to copy code: ', err);
  144. setButtonState(button, 'error', 'copy');
  145. });
  146. };
  147.  
  148. // Function to set the button state and color
  149. const setButtonState = (button, state, type) => {
  150. switch (state) {
  151. case 'working':
  152. button.textContent = type === 'download' ? 'Saving...' : 'Copied!';
  153. button.style.backgroundColor = 'green';
  154. break;
  155. case 'error':
  156. button.textContent = type === 'download' ? 'Could not download' : 'Could not copy';
  157. button.style.backgroundColor = 'red';
  158. break;
  159. case 'standby':
  160. default:
  161. button.textContent = button.dataset.defaultText || 'Unknown';
  162. button.style.backgroundColor = '#007bff'; // Blue
  163. break;
  164. }
  165. button.style.color = 'white';
  166.  
  167. // Revert the button to standby after 5 seconds
  168. if (state === 'working' || state === 'error') {
  169. setTimeout(() => {
  170. setButtonState(button, 'standby', type);
  171. }, 5000); // 5 seconds
  172. }
  173. };
  174.  
  175. // Function to add save and copy buttons to a code block
  176. const addButtonsToCodeBlock = (codeBlock) => {
  177. // Ensure existing buttons are not duplicated
  178. if (codeBlock.querySelector('.code-buttons-wrapper')) return;
  179.  
  180. const wrapper = document.createElement('div');
  181. wrapper.classList.add('code-buttons-wrapper');
  182. wrapper.style.position = 'relative';
  183. wrapper.style.display = 'flex';
  184. wrapper.style.justifyContent = 'flex-start';
  185. wrapper.style.gap = '8px';
  186. wrapper.style.marginTop = '8px';
  187.  
  188. const saveButton = document.createElement('button');
  189. saveButton.textContent = 'Save Code';
  190. styleButton(saveButton);
  191.  
  192. saveButton.addEventListener('click', (e) => {
  193. e.stopPropagation();
  194. const codeElement = codeBlock.querySelector('code');
  195. if (codeElement) {
  196. const text = codeElement.textContent;
  197. const languageClass = codeElement.className;
  198. const extension = getFileExtension(languageClass);
  199. downloadFile(text, `code.${extension}`, saveButton);
  200. }
  201. });
  202.  
  203. const copyButton = document.createElement('button');
  204. copyButton.textContent = 'Copy Code';
  205. styleButton(copyButton);
  206.  
  207. copyButton.addEventListener('click', (e) => {
  208. e.stopPropagation();
  209. const codeElement = codeBlock.querySelector('code');
  210. if (codeElement) {
  211. const text = codeElement.textContent;
  212. copyToClipboard(text, copyButton);
  213. }
  214. });
  215.  
  216. // Set default text for buttons
  217. saveButton.dataset.defaultText = 'Save Code';
  218. copyButton.dataset.defaultText = 'Copy Code';
  219.  
  220. codeBlock.parentNode.insertBefore(wrapper, codeBlock.nextSibling);
  221. wrapper.appendChild(saveButton);
  222. wrapper.appendChild(copyButton);
  223. };
  224.  
  225. // Function to style the buttons
  226. const styleButton = (button) => {
  227. Object.assign(button.style, {
  228. display: 'inline-block',
  229. padding: '8px',
  230. background: '#007bff', // Default standby color
  231. color: 'white',
  232. border: 'none',
  233. borderRadius: '4px',
  234. cursor: 'pointer',
  235. transition: 'background-color 0.3s ease',
  236. });
  237.  
  238. button.addEventListener('mouseover', () => {
  239. if (button.textContent === 'Saving...' || button.textContent === 'Copied!' || button.textContent === 'Could not download' || button.textContent === 'Could not copy') {
  240. button.style.backgroundColor = button.textContent.includes('Could not') ? 'darkred' : 'darkgreen';
  241. } else {
  242. button.style.backgroundColor = '#0056b3'; // Darker blue for standby
  243. }
  244. });
  245.  
  246. button.addEventListener('mouseout', () => {
  247. if (button.textContent === 'Saving...' || button.textContent === 'Copied!' || button.textContent === 'Could not download' || button.textContent === 'Could not copy') {
  248. button.style.backgroundColor = button.textContent.includes('Could not') ? 'red' : 'green';
  249. } else {
  250. button.style.backgroundColor = '#007bff'; // Default blue for standby
  251. }
  252. });
  253. };
  254.  
  255. // Function to observe code blocks and add buttons
  256. const observeCodeBlocks = () => {
  257. const codeBlocks = document.querySelectorAll('pre:not(.processed)');
  258. codeBlocks.forEach(block => {
  259. addButtonsToCodeBlock(block);
  260. block.classList.add('processed');
  261. });
  262. };
  263.  
  264. // Mutation observer to detect new code blocks added dynamically
  265. const observer = new MutationObserver(observeCodeBlocks);
  266. observer.observe(document.body, { childList: true, subtree: true });
  267.  
  268. // Initial call to process existing code blocks
  269. observeCodeBlocks();
  270.  
  271. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址